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
- Log/dump the offending bytes at the client and confirm the payload is complete, valid JSON (test with `jq`).
- Ensure the client sends exactly one JSON value per message with the framing the capsule expects — no length prefixes, NUL terminators, or header bytes.
- Fix truncation: verify the client flushes/closes the write side so the full payload is delivered before the capsule parses.
- 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
- Send exactly one UTF-8 JSON value per IPC message; never prepend length prefixes or NUL terminators unless the protocol defines them.
- Test the socket path with a reference client (jq-valid payload) before integrating custom code.
- Flush and close the write side so the capsule never parses a truncated body.
- Log a hex dump of rejected payloads at debug level to diagnose framing mismatches quickly.
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.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to deserialize IPC message payload
- failed to deserialize compaction response payload
- canonical document exceeds bound
- Mandatory publish key
- aos-mcp: malformed hook ingress
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)