astrid-runtime/astrid · critical

Daemon exited prematurely ({status}).{}

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.

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)

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.