astrid-runtime/astrid · error

running daemon declined live capsule unload

Error message

running daemon declined live capsule unload: {reason}

What it means

try_daemon_unload throws this when the running daemon explicitly responds with a KernelResponse::Error, carrying the daemon's reason for refusing the unload. The CLI surfaces that reason verbatim so the operator sees the daemon's actual denial (e.g. capability or permission failure) instead of a generic failure. This is a deliberate daemon-side rejection, not a transport or parse problem.

Solutions

  1. Read the daemon's reason embedded in the message and address it (e.g. stop the agent using the capsule before unloading).
  2. Restart the daemon if the refusal is caused by stale locks or sessions holding the capsule.
  3. Use --force-style removal paths or perform an offline uninstall if the daemon persistently refuses.
Defensive patterns

Strategy: try-catch

Validate before calling

// check daemon reachability and session permissions before unload
assert!(daemon_socket_reachable().await, "daemon not reachable");

Type guard

fn is_daemon_denial(e: &anyhow::Error) -> bool {
    e.to_string().contains("declined live capsule unload")
}

Try / catch

if let Err(e) = try_daemon_unload(...).await {
    if is_daemon_denial(&e) {
        // inspect daemon reason, stop dependent agents, retry once
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: Sending KernelRequest::UnloadCapsule to a live daemon that responds KernelResponse::Error(reason) — for instance the daemon refuses because the capsule is busy, another session holds it, or the requester lacks permission.

Common situations: Unloading a capsule that is actively in use by a running agent; the daemon's policy denies unload for privileged capsules; stale daemon state references a capsule locked by a crashed session.

Related errors


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

Appendix: source

Thrown at crates/astrid-cli/src/commands/capsule/live_load.rs:170

        .await
        .context("failed to send live capsule unload request")?;

    let raw = client
        .read_until_topic(response_topic.as_str(), std::time::Duration::from_secs(15))
        .await
        .context("running daemon did not confirm live capsule unload")?;

    match crate::socket_client::SocketClient::extract_kernel_response(&raw) {
        Some(KernelResponse::Success(data)) => {
            match data.get("status").and_then(serde_json::Value::as_str) {
                Some("unloaded") => Ok(LiveUnload::Unloaded),
                Some("not_loaded") => Ok(LiveUnload::NotLoaded),
                Some(other) => bail!("running daemon returned unknown unload status {other:?}"),
                None => bail!("running daemon returned unload success without a status"),
            }
        },
        Some(KernelResponse::Error(reason)) => {
            bail!("running daemon declined live capsule unload: {reason}")
        },
        _ => bail!("running daemon returned a malformed live capsule unload response"),
    }
}

async fn daemon_socket_reachable() -> bool {
    let path = crate::socket_client::proxy_socket_path();
    matches!(
        astrid_core::local_transport::connect_outcome(&path).await,
        Ok(astrid_core::local_transport::ConnectOutcome::Connected(_))
    )
}

fn classify_live_client<T>(
    result: crate::socket_client::WorkspaceConnectionResult<T>,
) -> anyhow::Result<Option<T>> {
    match result {
        Ok(client) => Ok(Some(client)),

View on GitHub (pinned to affd8760f4)