astrid-runtime/astrid · error

wire frame is an object

Error message

wire frame is an object

What it means

Panic from `frame.as_object_mut().expect("wire frame is an object")` in `write_message` (crates/astrid-uplink/src/native/mod.rs:530), called by `forward_outbound`. The code builds a serde_json::Value from a literal JSON object and, when a principal is present, inserts an extra key; the expect asserts the frame really is a JSON object. It fires only if the frame construction invariant is broken (the literal is no longer an object).

Solutions

  1. Inspect the frame construction lines just above: ensure the serde_json! literal is still an object ({...}).
  2. Replace expect with `if let Some(obj) = frame.as_object_mut()` or match to fail loudly with context during development.
  3. Add a unit test for messages carrying a principal so the object invariant is exercised.
  4. If the wire format intentionally changed, serialize principal into the new shape instead of inserting a key.

Example fix

// before
frame.as_object_mut().expect("wire frame is an object").insert(
    "principal".to_owned(),
    serde_json::Value::String(principal.clone()),
);
// after
let obj = frame.as_object_mut().ok_or_else(|| UplinkError::InvalidFrameShape)?;
obj.insert("principal".to_owned(), serde_json::Value::String(principal.clone()));
Defensive patterns

Strategy: type-guard

Validate before calling

// before inserting, narrow the frame
let Some(obj) = frame.as_object_mut() else {
    return Err(UplinkError::InvalidFrameShape);
};

Type guard

fn is_json_object(v: &serde_json::Value) -> bool { v.is_object() }

Try / catch

if !frame.is_object() {
    return Err(UplinkError::InvalidFrameShape);
}
frame.as_object_mut().unwrap().insert("principal".to_owned(), serde_json::Value::String(principal.clone()));

Prevention

When it happens

Trigger: Only when the serde_json::Value built immediately above is not Value::Object — i.e. a refactor changed the frame literal to an array/string/null while the insert path remained, or the frame was reassigned/replaced before the principal insert.

Common situations: Post-refactor regressions in the wire-format code: someone changed the frame payload shape (e.g. switched to an array envelope or a new top-level wrapper) without updating the principal-insert branch.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at crates/astrid-uplink/src/native/mod.rs:530

    Ok(())
}

async fn write_message(writer: &mut LocalWriteHalf, message: &IpcMessage) -> std::io::Result<()> {
    let payload_bytes = message
        .payload
        .to_guest_bytes()
        .map_err(|error| std::io::Error::other(format!("serialize IPC payload: {error}")))?;
    let payload: serde_json::Value = serde_json::from_slice(&payload_bytes)
        .map_err(|error| std::io::Error::other(format!("decode IPC payload: {error}")))?;
    let mut frame = serde_json::json!({
        "topic": message.topic,
        "payload": payload,
        "source_id": message.source_id,
    });
    if let Some(principal) = &message.principal {
        frame
            .as_object_mut()
            .expect("wire frame is an object")
            .insert(
                "principal".to_owned(),
                serde_json::Value::String(principal.clone()),
            );
    }
    let bytes = serde_json::to_vec(&frame)
        .map_err(|error| std::io::Error::other(format!("serialize IPC message: {error}")))?;
    let len = u32::try_from(bytes.len())
        .map_err(|_| std::io::Error::other("IPC message exceeds 4 GiB"))?;
    tokio::time::timeout(WRITE_TIMEOUT, async {
        writer.write_all(&len.to_be_bytes()).await?;
        writer.write_all(&bytes).await?;
        writer.flush().await
    })
    .await
    .map_err(|_| std::io::Error::new(std::io::ErrorKind::TimedOut, "IPC write timed out"))?
}

View on GitHub (pinned to affd8760f4)