astrid-runtime/astrid · error
shutdown stage daemon.process_reap: shutdown was acknowledge
Error message
shutdown stage daemon.process_reap: shutdown was acknowledged but no recorded PID exists, so process exit cannot be verified (listener: {}) What it means
An ACK to Shutdown only means 'shutting down', not that the process exited and released the singleton lock. confirm_graceful_stop verifies exit via the PID recorded BEFORE the shutdown request. If no PID file existed at that time, exit cannot be verified, so the CLI refuses to claim success (and refuses to delete markers blindly), bailing with the staged daemon.process_reap message including the socket path.
Source
Thrown at crates/astrid-cli/src/commands/daemon.rs:807
None => daemon_control::KillOutcome::NotRunning,
};
let disposition = confirm_kill_outcome(outcome)?;
cleanup_daemon_runtime(&socket_path, &pid_path).await?;
Ok(disposition)
}
/// After a graceful shutdown ACK, confirm the daemon process actually exited —
/// an ACK is "shutting down", not "exited and released the lock". Wait for the
/// recorded PID to die; if it wedges past the grace window it is still holding
/// the lock, so escalate through the same identity-gated signal path as an
/// unreachable orphan. Runtime files are cleaned only once the process is gone.
async fn confirm_graceful_stop(
recorded: Option<daemon_control::DaemonIdentity>,
socket_path: &Path,
pid_path: &Path,
) -> Result<DaemonStopDisposition> {
let Some(identity) = recorded else {
anyhow::bail!(
"shutdown stage daemon.process_reap: shutdown was acknowledged but no recorded PID exists, so process exit cannot be verified (listener: {})",
socket_path.display()
);
};
if daemon_control::wait_for_exit(identity.pid, daemon_control::GRACE).await {
return Ok(DaemonStopDisposition::Graceful);
}
// Acknowledged but still alive past the grace window → wedged mid-shutdown,
// still holding the lock. Escalate with a signal (identity-gated).
eprintln!(
"{}",
theme::Theme::warning(
"Daemon acknowledged shutdown but is still running; escalating with a signal so the \
state-db lock is released."
)
);View on GitHub (pinned to affd8760f4)
Solutions
- Ensure the CLI and daemon share the same AX_HOME / runtime dir so pid_path resolves correctly.
- Find the daemon process manually (pgrep astrid) and stop it, then remove stale socket/pid markers.
- Re-run `astrid stop` — after markers are consistent, the recorded-identity path works.
- Check daemon logs for why it started without persisting its PID file.
Example fix
// before Error: shutdown stage daemon.process_reap: shutdown was acknowledged but no recorded PID exists ... // after $ export AX_HOME=~/.astrid # same home the daemon uses $ pgrep -f astrid-daemon && kill -TERM <pid> $ astrid stop
Defensive patterns
Strategy: validation
Validate before calling
// verify a PID file exists (and is readable) before stopping
let pid_path = socket_client::pid_path();
if !pid_path.try_exists()? {
eprintln!("no recorded PID at {}; stop cannot verify exit", pid_path.display());
} Type guard
fn has_recorded_identity(pid_path: &std::path::Path) -> bool {
std::fs::read_to_string(pid_path).map(|s| !s.trim().is_empty()).unwrap_or(false)
} Try / catch
match stop_daemon().await {
Err(e) if e.to_string().contains("no recorded PID exists") => {
eprintln!("ACK without PID file; locate daemon manually");
manual_reap_and_clean()?;
}
r => r?,
} Prevention
- Always start the daemon through `astrid start` so it persists its PID file.
- Keep AX_HOME identical between the daemon's environment and any stop scripts.
- Never delete daemon.pid while the daemon runs.
- Use `astrid restart` instead of ad-hoc marker deletion to keep runtime files consistent.
When it happens
Trigger: Graceful stop path where the daemon ACKs Shutdown but daemon_control::read_daemon_identity(&pid_path) returned None — the pid file was missing before the request was sent (daemon started without writing it, ran under a different home/AX_HOME, or markers were already deleted).
Common situations: Daemon spawned by a different/older build that didn't persist a PID file; AX_HOME or runtime-dir env differing between the daemon and CLI so the CLI reads the wrong pid path; someone manually deleted the pid file while the daemon still runs; container where the daemon runs in another namespace.
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.shutdown_ack: rejected: {reason}
- daemon metadata lookup failed: {error}
- unexpected daemon metadata response: {other:?}
- daemon rejected capsule metadata request: {message}
- unexpected daemon response: {other:?}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/65c0ab9b8d638a2c.
Report an issue: GitHub.