unicity-aos/aos-ce · warning
aos-mcp: malformed hook ingress
Error message
aos-mcp: malformed {expected_host} hook ingress: {error} What it means
The MCP capsule's host-hook ingress handler (`handle`) deserializes the incoming payload into HostHookRequest. When serde_json cannot parse the payload, the hook is rejected as `malformed_payload`, a warning is logged naming the host and serde error, and the handler returns Ok(()) — the payload is dropped, not executed.
Solutions
- Inspect the logged serde error and fix the payload to match HostHookRequest's fields and types.
- Ensure required fields (event, principal_id) are present and correctly typed in the ingress payload.
- Verify the sending host bridge version matches the capsule's expected hook schema.
Example fix
// before (host side)
send_hook(host, json!({ "event": "tool_call" }))
// after
send_hook(host, json!({ "event": "tool_call", "principal_id": "abc-123" })) Defensive patterns
Strategy: try-catch
Validate before calling
// Host side: validate payload shape before sending
if payload.get("principal_id").is_none() || payload.get("event").is_none() {
panic!("hook ingress payload missing required fields");
} Type guard
fn is_valid_hook_payload(v: &serde_json::Value) -> bool {
v.get("event").and_then(|e| e.as_str()).is_some()
&& v.get("principal_id").and_then(|p| p.as_str()).is_some()
} Try / catch
match serde_json::from_value::<HostHookRequest>(payload) {
Ok(req) => handle(req),
Err(e) => log::warn!("malformed hook ingress: {e}"),
} Prevention
- Share the HostHookRequest struct/types between host bridge and capsule.
- Add contract tests that serialize and deserialize sample hook payloads.
- Monitor the malformed_payload rejection metric for schema drift.
When it happens
Trigger: Host sends a hook ingress payload to handle(expected_host, payload) that is not valid JSON for HostHookRequest (missing required fields like event/principal_id, wrong types, or invalid JSON).
Common situations: Host bridge version drift producing a different payload schema, manual curl testing against the hook with a hand-written body, or serialization bugs in the sending side.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- aos-mcp: malformed host-hook bridge response
- canonical document exceeds bound
- aos-mcp: caller unavailable for
- aos-mcp: caller unavailable for host-hook response
- must not be empty
AI-assisted analysis of unicity-aos/aos-ce@f6f22024fb (2026-09-13).
Data as JSON: /api/errors/6be5d00f263cabd3.
Report an issue: GitHub.
Appendix: source
Thrown at capsules/capsule-mcp/src/host_hooks.rs:69
host: String,
session_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
canonical_hook: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
event: Option<String>,
correlation_id: String,
route_id: String,
delivery_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
context: Option<String>,
}
pub(crate) fn handle(expected_host: &str, payload: serde_json::Value) -> Result<(), SysError> {
let request: HostHookRequest = match serde_json::from_value(payload) {
Ok(request) => request,
Err(error) => {
reject(expected_host, "unknown", "malformed_payload");
log::warn(format!(
"aos-mcp: malformed {expected_host} hook ingress: {error}"
));
return Ok(());
}
};
if let Err(reason) = validate_request(expected_host, &request) {
reject(expected_host, &request.event, reason);
return Ok(());
}
let caller = match runtime::caller() {
Ok(caller) => caller,
Err(error) => {
reject(expected_host, &request.event, "caller_unavailable");
log::warn(format!(
"aos-mcp: caller unavailable for {expected_host} hook: {error}"
));
return Ok(());View on GitHub (pinned to f6f22024fb)