astrid-runtime/astrid · error

Daemon error: {err}

Error message

Daemon error: {err}

What it means

resolve_commands asks the daemon for its external command list (GetCommands). When the daemon replies with KernelResponse::Error(err), the CLI deliberately surfaces the daemon's own error message as 'Daemon error: {err}' instead of folding it into a generic unexpected-response error. Per the source comment this typically represents a capability or permission denial on the daemon side.

Source

Thrown at crates/astrid-cli/src/commands/capsule_verb.rs:198

    let msg = astrid_types::ipc::IpcMessage::new(
        astrid_types::Topic::kernel_request("get_commands"),
        astrid_types::ipc::IpcPayload::RawJson(val),
        source_id,
    )
    .with_principal(caller.to_string());
    client.send_message(msg).await?;
    let raw = client
        .read_until_topic(
            astrid_types::Topic::kernel_response("get_commands").as_str(),
            Duration::from_secs(10),
        )
        .await?;
    match SocketClient::extract_kernel_response(&raw) {
        Some(astrid_core::kernel_api::KernelResponse::Commands(cmds)) => Ok(cmds),
        // Surface the daemon's own error (e.g. a capability/permission denial)
        // instead of folding it into a generic "unexpected response".
        Some(astrid_core::kernel_api::KernelResponse::Error(err)) => {
            anyhow::bail!("Daemon error: {err}")
        },
        _ => anyhow::bail!("Daemon returned an unexpected response to GetCommands"),
    }
}

/// Publish the run request and await + render the result.
async fn execute(provider: &str, verb: &str, args: &[String]) -> Result<ExitCode> {
    let session = astrid_core::SessionId::from_uuid(Uuid::new_v4());
    let source_id = session.0;
    // Bind the connection to the active principal so the capsule verb runs
    // under the invoking identity's context (VFS/KV/secrets), not the
    // `default` (admin) principal a nil/unstamped message falls back to.
    let caller = crate::principal::current();
    let mut client =
        match crate::socket_client::connect_for_workspace(session, caller.clone(), None).await {
            Ok(c) => c,
            Err(e) => {
                eprintln!(

View on GitHub (pinned to affd8760f4)

Solutions

  1. Read the embedded daemon reason and grant the required capability/permission for the caller in the daemon policy.
  2. Re-authenticate the session with the daemon and retry.
  3. Restart the daemon if its policy state is stale after an upgrade.
Defensive patterns

Strategy: try-catch

Validate before calling

// probe capabilities before resolving external verbs
let caps = client.request(KernelRequest::GetCapabilities).await?;
if !caps.allows_external_verbs() {
    eprintln!("principal lacks capability for external capsule verbs");
}

Type guard

fn is_daemon_denial(e: &anyhow::Error) -> bool {
    e.to_string().starts_with("Daemon error:")
}

Try / catch

match resolve_commands(...).await {
    Err(e) if is_daemon_denial(&e) => {
        // read the daemon reason; request the capability or re-authenticate
    }
    r => r,
}

Prevention

When it happens

Trigger: Invoking a command that must resolve its verb list via the daemon (external capsule verbs) while the daemon denies GetCommands — e.g. the caller lacks the required capability, or the daemon rejects the session/workspace context.

Common situations: Running external capsule verbs without the daemon permission configured for your principal; daemon policy changes after an upgrade; expired or unauthenticated daemon session.

Related errors


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