astrid-runtime/astrid · critical

Daemon exited prematurely

Error message

Daemon exited prematurely ({status}).{}

What it means

After the CLI spawns an (ephemeral) kernel daemon, it polls a readiness sentinel file until the daemon is ready or the spawn timeout elapses. If the child process terminates before signaling readiness, wait_for_ready reports ChildExited(status) and this error is raised, pointing the operator at the daemon's boot log via log_hint(). It means the daemon failed to boot at all rather than merely being slow.

Solutions

  1. Read the daemon boot log (see the log hint appended to the message) for the actual panic/error at exit.
  2. Run `astrid status` / check for a live daemon or leftover PID file; run `astrid restart` to clean stale endpoints and PID files.
  3. Verify the workspace path exists and is writable, and that ASTRID_WORKSPACE_STATE_DIR matches the expected layout.
  4. Rebuild/reinstall the daemon binary so CLI and daemon versions match, then retry.

Example fix

# before
$ astrid daemon start
Error: Daemon exited prematurely (exit status: 101).
# after: diagnose from the boot log
$ cat ~/.local/state/astrid/daemon-boot.log
$ astrid restart
$ astrid daemon start  # OK
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight checks before spawning the daemon
fn preflight_daemon(workspace_root: &Path) -> anyhow::Result<()> {
    anyhow::ensure!(workspace_root.is_dir(), "workspace does not exist: {}", workspace_root.display());
    let pid_alive = recorded_daemon_pid_is_alive();
    anyhow::ensure!(!pid_alive, "daemon already recorded as running; run `astrid restart`");
    Ok(())
}

Try / catch

match spawn_daemon_inner(ready_path, announce, None).await {
    Err(e) if e.to_string().contains("exited prematurely") => {
        let log = std::fs::read_to_string(boot_log_path()).unwrap_or_default();
        eprintln!("daemon boot failed; log tail:\n{}", last_lines(&log, 20));
        // clean up stale endpoint/PID then retry once
        astrid_core::local_transport::remove_stale_endpoint(&socket_path)?;
        spawn_daemon_inner(ready_path, announce, None).await
    },
    other => other,
}

Prevention

When it happens

Trigger: spawn_daemon -> spawn_daemon_inner spawning `astrid-daemon --ephemeral --workspace <root>` and the child exits before writing the readiness file — lock contention, panic before tracing init, bad workspace path, incompatible state layout, or a missing/corrupt data dir.

Common situations: Another daemon holds the singleton lock and died leaving inconsistent state; workspace path doesn't exist or lacks permissions; daemon binary version incompatible with on-disk layout; port/socket path conflicts; corrupted audit/state files failing import at boot.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/9fc5d0bb0bd9486a. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-cli/src/commands/daemon.rs:128

        .stderr(stderr);

    // Remove stale readiness file before spawning so we don't
    // mistake a leftover from a crashed daemon for the new one.
    let _ = std::fs::remove_file(ready_path);

    let mut child = cmd
        .spawn()
        .context("Failed to spawn background Kernel daemon")?;

    // Poll for the readiness sentinel instead of the socket file.
    // The readiness file is written only after load_all_capsules()
    // completes (including await_capsule_readiness()), so the accept
    // loop is guaranteed to be running by the time we connect.
    let timeout_secs = configured_spawn_timeout_secs(workspace_root);
    match wait_for_ready(ready_path, &mut child, timeout_secs).await {
        ReadyWaitOutcome::Ready => Ok(child),
        ReadyWaitOutcome::ChildExited(status) => {
            anyhow::bail!("Daemon exited prematurely ({status}).{}", log_hint());
        },
        ReadyWaitOutcome::StillRunning => {
            // Do not SIGKILL a live first cutover (layout-1 audit import
            // can outlive the wait). Disown and tell the operator.
            disown_if_still_running(child);
            anyhow::bail!(
                "Daemon is still starting after {timeout_secs} seconds; it was left running. Check logs or run `astrid status` / retry later.{}",
                log_hint()
            );
        },
    }
}

fn ephemeral_daemon_command(daemon_bin: &Path, workspace_root: &Path) -> std::process::Command {
    let mut cmd = std::process::Command::new(daemon_bin);
    cmd.arg("--ephemeral")
        .arg("--workspace")
        .arg(workspace_root)

View on GitHub (pinned to affd8760f4)