astrid-runtime/astrid · critical

The previous Astrid daemon

Error message

The previous Astrid daemon (PID {pid}) did not exit even after SIGKILL; refusing to start a second daemon while the lock may still be held.

What it means

`astrid restart` stops the running daemon, then verifies the old PID is gone before spawning a replacement. If `terminate_orphan` reports `StillAlive` — the process survived even SIGKILL — the CLI refuses to start a second daemon, because the stale process may still hold the daemon lock and a second instance would fail or corrupt state.

Solutions

  1. Inspect the PID: `ps -o pid,stat,wchan,cmd -p <pid>` to see why it won't die
  2. Resolve the blocking condition (unstick the mount, reap the zombie via its parent, wait for D-state I/O)
  3. If the PID is a different live process (reuse), remove the pid_path file and retry after confirming it is not Astrid
  4. Reboot or restart the container/host if the process is truly unkillable

Example fix

# before (blind retry)
astrid restart
# after
ps -o pid,stat,wchan,cmd -p $(cat /path/to/astrid.pid)
# resolve the stuck process, then
astrid restart
Defensive patterns

Strategy: try-catch

Validate before calling

pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
  echo "PID $pid still alive; resolve before restart" >&2
  exit 1
fi

Try / catch

match daemon_control::terminate_orphan(&pid_path).await {
    daemon_control::KillOutcome::StillAlive => {
        eprintln!("old daemon unkillable; inspect with ps and resolve blocking I/O");
        std::process::exit(1);
    }
    _ => { /* safe to restart */ }
}

Prevention

When it happens

Trigger: The recorded PID in the pid file still exists after stop+SIGKILL: process stuck in uninterruptible sleep (D state, e.g. blocked on NFS/FUSE I/O), zombie held by a non-reaping parent, or PID reuse where the new owner ignores signals.

Common situations: Daemon wedged on unkillable I/O in containers with stale mounts, Docker/Kubernetes containers with paused PIDs, or a hung syscall during shutdown.

Related errors


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

Appendix: source

Thrown at crates/astrid-cli/src/commands/restart.rs:68

    // released the singleton/state-db lock — a wedged daemon `handle_stop`
    // could not reach over the socket may still be alive. Spawning now would
    // race the new daemon against a held lock and fail with "Database … is
    // already locked". Verify the recorded PID is actually gone (signalling it
    // if it survived `handle_stop`'s own kill path) before spawning.
    let pid_path = socket_client::pid_path();
    if let Some((pid, _exe)) = daemon_control::read_pid_file(&pid_path)
        && daemon_control::is_process_alive(pid)
    {
        eprintln!(
            "{}",
            Theme::warning(&format!(
                "A process holding the recorded daemon PID {pid} is still alive after stop — \
                 verifying before restart."
            ))
        );
        match daemon_control::terminate_orphan(&pid_path).await {
            daemon_control::KillOutcome::StillAlive => {
                anyhow::bail!(
                    "The previous Astrid daemon (PID {pid}) did not exit even after SIGKILL; \
                     refusing to start a second daemon while the lock may still be held."
                );
            },
            daemon_control::KillOutcome::Unverified(p) => {
                // Fail-secure: a live PID we can't confirm is the daemon (likely
                // PID reuse) is NOT killed. Don't blindly spawn either — if it
                // *were* the daemon, the lock is still held and the spawn would
                // fail on it anyway. Make the operator resolve it explicitly.
                anyhow::bail!(
                    "Cannot confirm PID {p} is the Astrid daemon (possible PID reuse); \
                     refusing to auto-restart. Inspect PID {p} — if it is not Astrid, \
                     remove {} and retry.",
                    pid_path.display()
                );
            },
            _ => {
                let _ = std::fs::remove_file(&pid_path);

View on GitHub (pinned to affd8760f4)