astrid-runtime/astrid · error

Daemon returned an unexpected response to GetCommands

Error message

Daemon returned an unexpected response to GetCommands

What it means

When the CLI resolves the list of capsule commands, it sends a `get_commands` kernel request over the IPC socket and expects the daemon to reply with a KernelResponse::Commands payload. This error is thrown when the daemon's reply is neither Commands nor an explicit KernelResponse::Error — i.e. the response shape does not match what the CLI's GetCommands handler can interpret. It indicates a protocol/version mismatch between CLI and daemon, a corrupted/interleaved frame, or the request never reaching the kernel's command resolver.

Source

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

        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!(
                    "{}",
                    Theme::error(&format!("Failed to connect to daemon: {e}"))

View on GitHub (pinned to affd8760f4)

Solutions

  1. Restart the daemon so CLI and daemon versions match: run `astrid restart` (or kill the daemon process and let the next command spawn a fresh one).
  2. Verify the astrid-cli and astrid-core/daemon binaries come from the same build (`astrid --version` vs the daemon binary's version) and reinstall/rebuild together.
  3. Check the daemon boot log for panics or protocol errors around the get_commands request, then retry the command.
  4. If the error is reproducible on a fresh daemon, file a bug with the raw frame; the variant returned by extract_kernel_response indicates what broke the protocol contract.

Example fix

// before: single shared daemon across mixed versions
$ astrid daemon start   # old binary still running
$ astrid run mycapsule cmd  # Daemon returned an unexpected response to GetCommands
// after
$ astrid restart        # kills stale daemon, spawns matching build
$ astrid run mycapsule cmd  # OK
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check daemon/CLI version agreement and liveness before running capsule verbs
async fn daemon_compatible(client: &mut SocketClient) -> anyhow::Result<bool> {
    let raw = client.read_until_topic(Topic::kernel_response("ping").as_str(), Duration::from_secs(5)).await?;
    Ok(SocketClient::extract_kernel_response(&raw)
        .map(|r| matches!(r, KernelResponse::Commands(_) | KernelResponse::Pong))
        .unwrap_or(false))
}

Type guard

fn is_expected_kernel_response(raw: &[u8]) -> Option<KernelResponse> {
    serde_json::from_slice::<KernelResponse>(raw).ok()
        .filter(|r| matches!(r, KernelResponse::Commands(_) | KernelResponse::Error(_)))
}

Try / catch

match resolve_commands(client).await {
    Ok(cmds) => render(cmds),
    Err(e) if e.to_string().contains("unexpected response to GetCommands") => {
        eprintln!("CLI/daemon protocol mismatch; run `astrid restart`");
        // optionally retry once after restart
    },
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling any `astrid` capsule verb that runs resolve_commands (via run_external or run_explicit) when the daemon replies to the get_commands topic with a payload that deserializes to a KernelResponse variant other than Commands or Error (e.g. an empty, malformed, or unrelated response, or a daemon built from a different version).

Common situations: Mixed-version installs where the daemon binary was updated but a stale daemon from an older version is still running; a proxy/middleware on the socket rewriting frames; another client interleaving messages on the same connection so extract_kernel_response picks up the wrong frame.

Related errors


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