astrid-runtime/astrid · warning

shutdown stage daemon.process_identity: PID

Error message

shutdown stage daemon.process_identity: PID {pid} is live but cannot be verified as Astrid; no markers were removed

What it means

terminate_identity is identity-gated: before signalling a PID it verifies the process actually is Astrid (to protect an innocent process whose PID was recycled after a crash). If the recorded PID is alive but cannot be verified as an Astrid process, the CLI refuses to signal it and refuses to remove any runtime markers, bailing with the daemon.process_identity message so no unrelated process is ever killed.

Solutions

  1. Verify what the PID now belongs to (ps -p <pid> -o pid,cmd) — do NOT kill it manually if it is another service.
  2. Remove the stale pid/socket markers (after confirming no real astrid daemon runs via pgrep -f astrid), then run `astrid start`.
  3. Use `pgrep -f astrid-daemon` to locate the real daemon if one exists elsewhere.
  4. Avoid sharing the Astrid runtime dir across hosts to prevent PID-space confusion.

Example fix

// before
Error: shutdown stage daemon.process_identity: PID 4123 is live but cannot be verified as Astrid; no markers were removed
// after
$ ps -p 4123 -o cmd        # confirm it is NOT astrid
$ pgrep -f astrid-daemon   # confirm no real daemon exists
$ rm ~/.astrid/run/daemon.pid ~/.astrid/run/system.sock
$ astrid start
Defensive patterns

Strategy: validation

Validate before calling

// before stopping, confirm the recorded PID still is an astrid process
let pid = read_pid_file(&socket_client::pid_path())?;
let cmd = std::fs::read_to_string(format!("/proc/{pid}/cmdline"))?.replace('\0', " ");
if !cmd.contains("astrid") {
    eprintln!("PID {pid} was recycled by another process; clean markers instead of stopping");
}

Type guard

fn recorded_pid_is_astrid(pid: i32) -> bool {
    std::fs::read_to_string(format!("/proc/{pid}/cmdline"))
        .map(|c| c.replace('\0', " ").contains("astrid"))
        .unwrap_or(false)
}

Try / catch

match stop_daemon().await {
    Err(e) if e.to_string().contains("cannot be verified as Astrid") => {
        eprintln!("PID recycled; cleaning stale markers instead of signalling");
        clean_runtime_markers()?;
        run(vec!["astrid", "start"])?;
    }
    r => r?,
}

Prevention

When it happens

Trigger: Orphan-path stop (or escalated graceful stop) where KillOutcome::Unverified(pid) is returned: the PID from the pid file is live, but its executable/cmdline/start-time markers do not match the recorded daemon identity — i.e. the PID was recycled by a different program.

Common situations: Long uptime after a daemon crash, so the OS reassigned the PID to another service; pid file left over from a previous boot (PID wraparound); multiple machines sharing a runtime dir over a network mount.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

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

        )
    );
    let outcome = daemon_control::terminate_identity(&identity, pid_path).await;
    confirm_kill_outcome(outcome)
}

fn confirm_kill_outcome(outcome: daemon_control::KillOutcome) -> Result<DaemonStopDisposition> {
    match outcome {
        daemon_control::KillOutcome::NotRunning => Ok(DaemonStopDisposition::AlreadyStopped),
        daemon_control::KillOutcome::TermExited | daemon_control::KillOutcome::KilledExited => {
            Ok(DaemonStopDisposition::Forced)
        },
        daemon_control::KillOutcome::StillAlive => {
            anyhow::bail!(
                "shutdown stage daemon.process_reap: daemon did not exit after forced termination; the singleton lock may still be held"
            );
        },
        daemon_control::KillOutcome::Unverified(pid) => {
            anyhow::bail!(
                "shutdown stage daemon.process_identity: PID {pid} is live but cannot be verified as Astrid; no markers were removed"
            );
        },
    }
}

/// Fence marker cleanup with the same singleton lock the daemon owns. Holding
/// it through removal prevents a replacement daemon from publishing fresh
/// markers between liveness proof and cleanup.
async fn cleanup_daemon_runtime(socket_path: &Path, pid_path: &Path) -> Result<()> {
    let home = astrid_core::dirs::AstridHome::resolve()
        .context("shutdown stage daemon.home_resolution")?;
    cleanup_daemon_runtime_for_home(&home, socket_path, pid_path).await
}

async fn cleanup_daemon_runtime_for_home(
    home: &astrid_core::dirs::AstridHome,
    socket_path: &Path,

View on GitHub (pinned to affd8760f4)