unicity-aos/aos-ce · warning
aos-mcp: malformed host-hook bridge response
Error message
aos-mcp: malformed host-hook bridge response: {error} What it means
relay_response deserializes the host-hook bridge response payload into HostHookResponse. On serde failure it rejects with `malformed_response` (host/event "unknown"), logs a warning with the serde error, and returns Ok(()) — the response is dropped rather than relayed to a mismatched structure.
Solutions
- Fix the bridge's response payload to match HostHookResponse (host, event, principal_id, etc.).
- Check version alignment between the host bridge and the MCP capsule's response schema.
- Log/inspect the raw payload on the bridge side to spot truncation or encoding problems.
Example fix
// before (bridge)
respond(json!({ "host": host }))
// after
respond(json!({ "host": host, "event": event, "principal_id": principal_id })) Defensive patterns
Strategy: try-catch
Validate before calling
// Bridge side: verify response shape before delivering
if resp.get("principal_id").is_none() {
return Err("host-hook response missing principal_id");
} Type guard
fn is_valid_hook_response(v: &serde_json::Value) -> bool {
v.get("host").and_then(|h| h.as_str()).is_some()
&& v.get("principal_id").and_then(|p| p.as_str()).is_some()
} Try / catch
match serde_json::from_value::<HostHookResponse>(payload) {
Ok(resp) => relay(resp),
Err(e) => { reject("unknown", "unknown", "malformed_response"); log::warn!("malformed response: {e}"); }
} Prevention
- Keep the bridge's response serializer on the same version of the schema as the capsule.
- Add round-trip serialization tests for HostHookResponse.
- Watch malformed_response rejection logs for bridge regressions.
When it happens
Trigger: relay_response(payload) receives JSON that doesn't match HostHookResponse: missing principal_id, wrong event/host types, or non-JSON bytes.
Common situations: Host bridge emitting a response schema from an older/newer version, truncation or corruption of the payload in transit, or a bridge bug serializing optional fields incorrectly.
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 hook ingress
- aos-mcp: caller unavailable for
- aos-mcp: caller unavailable for host-hook response
- canonical document exceeds bound
- aos-mcp-broker: call install_aos before handling traffic
AI-assisted analysis of unicity-aos/aos-ce@f6f22024fb (2026-09-13).
Data as JSON: /api/errors/eb0506028d423b1f.
Report an issue: GitHub.
Appendix: source
Thrown at capsules/capsule-mcp/src/host_hooks.rs:124
host: &request.host,
session_id: &request.session_id,
event: &request.event,
correlation_id: &request.correlation_id,
route_id: &request.route_id,
delivery_id: &request.delivery_id,
turn_id: request.turn_id.as_deref(),
workspace_id: request.workspace_id.as_deref(),
payload: &request.payload,
},
)
}
pub(crate) fn relay_response(payload: serde_json::Value) -> Result<(), SysError> {
let response: HostHookResponse = match serde_json::from_value(payload) {
Ok(response) => response,
Err(error) => {
reject("unknown", "unknown", "malformed_response");
log::warn(format!(
"aos-mcp: malformed host-hook bridge response: {error}"
));
return Ok(());
}
};
let event = response.event.as_deref().unwrap_or("unknown");
if let Err(reason) = validate_response_shape(&response) {
reject(&response.host, event, reason);
return Ok(());
}
let caller = match runtime::caller() {
Ok(caller) => caller,
Err(error) => {
reject(&response.host, event, "caller_unavailable");
log::warn(format!(
"aos-mcp: caller unavailable for host-hook response: {error}"
));View on GitHub (pinned to f6f22024fb)