astrid-runtime/astrid · error
{primary:#}; additional shutdown failure: {secondary:#}
Error message
{primary:#}; additional shutdown failure: {secondary:#} What it means
combine_stop_results merges the two halves of `astrid stop` — the MCP gateway shutdown and the daemon shutdown. When BOTH fail, neither failure can be silently dropped, so the primary is reported and the daemon-side failure is appended as 'additional shutdown failure: {secondary:#}' via anyhow's alternate formatting (the full error chain).
Source
Thrown at crates/astrid-cli/src/commands/daemon.rs:728
println!("{}", theme::Theme::success(message));
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DaemonStopDisposition {
AlreadyStopped,
Graceful,
Forced,
}
fn combine_stop_results(
gateway: Result<()>,
daemon: Result<DaemonStopDisposition>,
) -> Result<DaemonStopDisposition> {
match (gateway, daemon) {
(Ok(()), Ok(disposition)) => Ok(disposition),
(Err(primary), Ok(_)) | (Ok(()), Err(primary)) => Err(primary),
(Err(primary), Err(secondary)) => {
anyhow::bail!("{primary:#}; additional shutdown failure: {secondary:#}")
},
}
}
async fn stop_daemon() -> Result<DaemonStopDisposition> {
let socket_path = socket_client::proxy_socket_path();
let pid_path = socket_client::pid_path();
// Capture the daemon's identity up front: it deletes its own PID file only
// on a CLEAN exit, so reading it before shutdown is the only reliable way to
// keep a handle for confirming exit / signalling a wedged shutdown.
let recorded = daemon_control::read_daemon_identity(&pid_path);
let socket_present = astrid_core::local_transport::endpoint_is_present(&socket_path)
.context("failed to inspect daemon endpoint")?;
// Genuinely nothing running: no socket AND no live recorded process.
let recorded_alive = recorded
.as_ref()View on GitHub (pinned to affd8760f4)
Solutions
- Read the primary error first — fix that root cause; the secondary is usually the same underlying problem (e.g. permissions, wedged PID).
- If both report a stuck PID, SIGTERM/SIGKILL the recorded daemon PID manually and remove stale runtime files.
- Re-run `astrid stop` after cleanup; it is idempotent (AlreadyStopped path).
- Check runtime dir ownership/permissions if both failures mention file access.
Example fix
// before Error: gateway stop failed: ...; additional shutdown failure: shutdown stage daemon.shutdown_ack: rejected: ... // after $ kill -TERM <recorded-daemon-pid> && rm -f ~/.astrid/run/system.sock ~/.astrid/run/daemon.pid $ astrid stop
Defensive patterns
Strategy: try-catch
Validate before calling
null
Type guard
null
Try / catch
match stop_everything().await {
Err(e) if e.to_string().contains("additional shutdown failure") => {
// both legs failed: force-clean the recorded identity then retry once
force_clean_runtime()?;
stop_everything().await?;
}
r => r?,
} Prevention
- Treat the primary error as the root cause; the appended failure usually shares it.
- Run stop under the same AX_HOME/env as the daemon so both shutdown paths resolve the same run-dir.
- Avoid killing both gateway and daemon with external signals right before `astrid stop`.
- Verify runtime-dir permissions so neither leg fails on file access.
When it happens
Trigger: `astrid stop` (handle_stop) where stop_gateway() and stop_daemon() both return Err — e.g. the socket is unreachable AND the gateway process also fails to shut down (wedged child, permission problem, both holding stale locks).
Common situations: System under resource pressure so both processes are wedged; shared runtime-dir permission problems breaking both shutdown paths; a kill sweep (OOM, container stop) that left both components half-dead; stale PID files confusing both paths.
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
- unexpected env list response: {response:?}
- principal capsule removal must go through the authenticated
- Daemon returned an unexpected response to GetCommands
- Daemon exited prematurely ({status}).{}
- an Astrid daemon appears to be running but its uplink is unr
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/a71cf6dbfa6fb7d0.
Report an issue: GitHub.