astrid-runtime/astrid · error

shutdown stage daemon.shutdown_ack: rejected: {reason}

Error message

shutdown stage daemon.shutdown_ack: rejected: {reason}

What it means

When `astrid stop` sends KernelRequest::Shutdown over the socket, the daemon should ACK with KernelResponse::Success. If it instead replies KernelResponse::Error(reason), the daemon refused to begin shutting down, and the CLI bails with the staged message naming the rejection reason so the user knows shutdown never started.

Source

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

    // Graceful path: the socket is present and serviceable.
    // Deliberately bypass the selected-workspace check: stopping a daemon is
    // the recovery path when that daemon belongs to another project/layout.
    if socket_present && let Ok(client) = socket_client::connect_kernel_for_recovery().await {
        let mut client = client.with_timeout(Duration::from_secs(10));
        let response = client
            .request(KernelRequest::Shutdown {
                reason: Some("astrid stop".to_string()),
            })
            .await
            .context("shutdown stage daemon.shutdown_ack")?;
        let disposition = match response {
            KernelResponse::Success(_) => {
                // ACK only — confirm the process actually exits before
                // declaring success, and escalate if it wedged.
                confirm_graceful_stop(recorded, &socket_path, &pid_path).await?
            },
            KernelResponse::Error(reason) => {
                anyhow::bail!("shutdown stage daemon.shutdown_ack: rejected: {reason}")
            },
            other => {
                anyhow::bail!("shutdown stage daemon.shutdown_ack: unexpected response: {other:?}")
            },
        };
        cleanup_daemon_runtime(&socket_path, &pid_path).await?;
        return Ok(disposition);
    }

    // Orphan path: the socket is present but unreachable (hung/half-dead
    // daemon), OR the socket is already gone but a live recorded daemon is still
    // holding the lock. A clean shutdown request is impossible either way, so
    // signal the recorded PID (identity-gated) and clean up. Using the PID we
    // captured up front — not a re-read — closes the window where the daemon
    // deletes its own PID file mid-wedge.
    let outcome = match recorded.as_ref() {
        Some(identity) => daemon_control::terminate_identity(identity, &pid_path).await,
        None => daemon_control::KillOutcome::NotRunning,

View on GitHub (pinned to affd8760f4)

Solutions

  1. Read the {reason} in the message — it states why the daemon refused.
  2. Retry `astrid stop` once the transient condition (load/lease) clears.
  3. If shutdown is persistently rejected, use `astrid restart`, which force-terminates via the identity-gated signal path.
  4. Check daemon logs for the handler that produced the rejection.

Example fix

// before
$ astrid stop
Error: shutdown stage daemon.shutdown_ack: rejected: capsules still loading
// after
$ sleep 5 && astrid stop   # or: astrid restart to force-terminate
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

fn is_shutdown_ack(resp: &KernelResponse) -> bool {
    matches!(resp, KernelResponse::Success(_))
}

Try / catch

match stop_daemon().await {
    Err(e) if e.to_string().contains("daemon.shutdown_ack: rejected") => {
        eprintln!("daemon refused shutdown: {e}; falling back to restart");
        run(vec!["astrid", "restart"])?;
    }
    r => r?,
}

Prevention

When it happens

Trigger: stop_daemon's graceful path: the socket is present, connect_kernel_for_recovery succeeds, the Shutdown request is delivered, and the daemon responds with KernelResponse::Error(reason) instead of Success — e.g. it believes shutdown is invalid in its current state or an internal precondition failed.

Common situations: Daemon mid-startup or mid-capsule-load refusing shutdown; daemon with active sessions/leases it considers non-interruptible; daemon in a degraded state whose shutdown handler returned a validation error; version skew where the daemon doesn't recognize the request payload.

Related errors


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