astrid-runtime/astrid · error

running daemon returned a malformed live capsule unload…

Error message

running daemon returned a malformed live capsule unload response

What it means

try_daemon_unload throws this when the raw response read from the daemon socket cannot be extracted into any recognized KernelResponse variant at all. This means the message on the response topic was malformed, of an unexpected type, or absent expected structure — a transport-level or schema-level anomaly rather than a daemon decision.

Solutions

  1. Restart the daemon and retry the unload to rule out a one-off corrupted message.
  2. Verify CLI and daemon versions match (same release) so the IPC schema agrees.
  3. Inspect the daemon logs and the socket traffic for a foreign publisher writing to the kernel response topic.
Defensive patterns

Strategy: retry

Validate before calling

// ensure only the expected daemon is bound to the proxy socket
let reachable = daemon_socket_reachable().await;

Type guard

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

Try / catch

match try_daemon_unload(...).await {
    Err(e) if is_malformed_response(&e) => {
        // retry once after reconnecting; then restart daemon
    }
    r => r?,
}

Prevention

When it happens

Trigger: UnloadCapsule response read via read_until_topic does not parse as KernelResponse::Success or KernelResponse::Error (corrupt JSON, wrong topic payload, foreign message on the kernel_response topic, schema drift).

Common situations: Mixed CLI/daemon versions exchanging incompatible IPC payloads; another process publishing onto the same MQTT/socket topic; truncated or corrupted IPC message under load.

Understand the failure class

Related errors


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

Appendix: source

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

    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)),
        Err(crate::socket_client::WorkspaceConnectionError::Connect(_)) => Ok(None),
        Err(crate::socket_client::WorkspaceConnectionError::Selection(error)) => Err(error),

View on GitHub (pinned to affd8760f4)