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
- Verify what the PID now belongs to (ps -p <pid> -o pid,cmd) — do NOT kill it manually if it is another service.
- Remove the stale pid/socket markers (after confirming no real astrid daemon runs via pgrep -f astrid), then run `astrid start`.
- Use `pgrep -f astrid-daemon` to locate the real daemon if one exists elsewhere.
- 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
- Never manually delete daemon.pid while the daemon is healthy — it enables PID-recycle confusion.
- After a crash, clean markers promptly so PIDs aren't reused while stale files linger.
- Check /proc/<pid>/cmdline before any manual kill of a recorded PID.
- Don't share one Astrid runtime dir across machines or containers with independent PID spaces.
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
- shutdown stage daemon.process_reap: shutdown was…
- Admin request timed out after
- an Astrid daemon appears to be running but its uplink is…
- an Astrid daemon appears to be running but its uplink is…
- an Astrid daemon is recorded as running (PID file) but its…
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)