unicity-aos/aos-ce · warning

Received malformed IPC payload from socket

Error message

Received malformed IPC payload from socket

What it means

This warning is logged in `handle_ingress` (capsule-cli) when an incoming IPC payload on the socket is not valid JSON — serde_json::from_slice fails. The capsule ignores the message and returns an empty response rather than panicking, so the sender gets no indication beyond this log line. It indicates a protocol mismatch between the IPC client and the capsule.

Solutions

  1. Log/dump the offending bytes at the client and confirm the payload is complete, valid JSON (test with `jq`).
  2. Ensure the client sends exactly one JSON value per message with the framing the capsule expects — no length prefixes, NUL terminators, or header bytes.
  3. Fix truncation: verify the client flushes/closes the write side so the full payload is delivered before the capsule parses.
  4. Confirm client and capsule agree on JSON encoding (UTF-8, serde_json::Value object with fields like `principal`).

Example fix

// before: sending extra framing
socket.write_all(format!("{}\n", len).as_bytes())?;
socket.write_all(&payload)?;
// after: send the JSON document only
socket.write_all(&serde_json::to_vec(&msg)?)?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Client-side: validate before writing to the IPC socket
let payload = serde_json::to_vec(&msg).expect("serializable");
assert!(serde_json::from_slice::<serde_json::Value>(&payload).is_ok(), "payload must be one valid JSON value");
socket.write_all(&payload)?;

Try / catch

let msg = match serde_json::from_slice::<serde_json::Value>(bytes) {
    Ok(v) => v,
    Err(e) => {
        log::warn!("malformed IPC payload: {e} ({} bytes)", bytes.len());
        return empty;
    }
};

Prevention

When it happens

Trigger: A client writes raw bytes, partial JSON, binary data, or a non-JSON protocol frame (e.g. length-prefixed body with header bytes included) to the capsule's IPC socket, so serde_json cannot parse the slice.

Common situations: Hand-testing the socket with curl/netcat sending non-JSON; client framing mismatch (extra length prefix or NUL terminator); truncated write from a crashed client; sending protobuf/MessagePack instead of JSON; encoding issues.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


AI-assisted analysis of unicity-aos/aos-ce@f6f22024fb (2026-09-13). Data as JSON: /api/errors/a99bd8ad5a89255f. Report an issue: GitHub.

Appendix: source

Thrown at capsules/capsule-cli/src/lib.rs:553

/// Parse an incoming client message, apply the per-connection binding state
/// machine ([`decide_ingress`]), and forward it to the IPC bus if the binding
/// allows it and the topic passes the ingress allowlist.
///
/// `current_binding` is the connection's principal so far (`None` until the
/// first usable message binds it). Returns an [`IngressOutcome`] carrying the
/// newly-bound principal (only on the binding message) and the conversation
/// session observed on this message, both of which the caller folds onto the
/// connection. A dropped/malformed message yields an empty outcome.
fn handle_ingress(bytes: &[u8], current_binding: Option<&str>) -> IngressOutcome {
    let empty = IngressOutcome {
        newly_bound: None,
        session_id: None,
    };

    let msg = match serde_json::from_slice::<serde_json::Value>(bytes) {
        Ok(v) => v,
        Err(_) => {
            log::warn("Received malformed IPC payload from socket");
            return empty;
        }
    };

    let message_principal = msg.get("principal").and_then(|p| p.as_str());

    // Resolve the binding decision first — a conflicting or malformed
    // principal is dropped before any forward, and never mutates the binding.
    let (forward_as, newly_bound) = match decide_ingress(current_binding, message_principal) {
        IngressDecision::Bind(p) => (p.clone(), Some(p)),
        IngressDecision::ForwardAs(p) => (p, None),
        IngressDecision::Drop { reason } => {
            match reason {
                DropReason::InvalidPrincipal(p) => log::warn(format!(
                    "Dropped ingress message: malformed principal {p:?}; connection stays unbound"
                )),
                DropReason::PrincipalConflict { bound, claimed } => log::warn(format!(
                    "Dropped ingress message: connection bound to {bound:?} but message claimed {claimed:?}"

View on GitHub (pinned to f6f22024fb)