unicity-aos/aos-ce · warning

meta-harness: malformed canonical payload

Error message

meta-harness: malformed canonical payload: {error}

What it means

After the HookRequest parses and a correlation_id routes it, meta-harness parses request.payload (a JSON string) into CanonicalHostPayload. If the embedded canonical payload string does not deserialize, this warning is logged and the hook returns Ok(()), skipping reflection for the turn.

Solutions

  1. Print request.payload and validate it against CanonicalHostPayload's expected fields; fix the emitting side.
  2. Add or relax serde attributes (deny_unknown_fields removal, Option fields) to match the current canonical schema.
  3. Ensure the sender serializes the canonical payload as a JSON string, not an object, if the struct expects from_str.
  4. Pin/upgrade capsule-meta-harness and payload producer to matching versions.

Example fix

// before
let canonical: CanonicalHostPayload = serde_json::from_str(&request.payload)?;
// after
// ensure payload contains required fields, e.g. {"source_event":"user_prompt_submit", ...}
let canonical: CanonicalHostPayload = serde_json::from_str(&request.payload)?;
Defensive patterns

Strategy: validation

Validate before calling

// validate inner canonical payload before dispatch
const inner = JSON.parse(request.payload);
if (inner.source_event !== 'user_prompt_submit') throw new Error('unsupported event');

Type guard

fn is_canonical(s: &str) -> bool {
    serde_json::from_str::<CanonicalHostPayload>(s).is_ok()
}

Try / catch

let canonical: CanonicalHostPayload = match serde_json::from_str(&request.payload) {
    Ok(c) => c,
    Err(e) => { log::warn("malformed canonical payload: {e}"); return Ok(()); }
};

Prevention

When it happens

Trigger: request.payload is a JSON string that fails serde_json::from_str::<CanonicalHostPayload> — inner JSON malformed, missing required fields like source_event, or wrong field types inside the nested payload.

Common situations: Producer of the canonical payload changed its schema (e.g., renamed source_event); double-encoding issues where payload is an object rather than a string; truncated payloads from upstream.

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.

Related errors


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

Appendix: source

Thrown at capsules/capsule-meta-harness/src/lib.rs:79

    pub fn on_message_received(&self, payload: serde_json::Value) -> Result<(), SysError> {
        let request: HookRequest = match serde_json::from_value(payload) {
            Ok(request) => request,
            Err(error) => {
                log::warn(format!("meta-harness: malformed hook request: {error}"));
                return Ok(());
            }
        };
        let Some(correlation_id) = request.correlation_id else {
            return Ok(());
        };
        if !is_correlation(&correlation_id) {
            log::warn("meta-harness: ignored unroutable hook correlation");
            return Ok(());
        }
        let canonical: CanonicalHostPayload = match serde_json::from_str(&request.payload) {
            Ok(canonical) => canonical,
            Err(error) => {
                log::warn(format!(
                    "meta-harness: malformed canonical payload: {error}"
                ));
                return Ok(());
            }
        };
        if canonical.source_event != "user_prompt_submit"
            || !matches!(canonical.host.as_str(), "codex" | "claude" | "grok")
            || canonical.session_id.is_empty()
        {
            return Ok(());
        }
        let Some(context) = activation_context()? else {
            return Ok(());
        };
        ipc::publish_json(
            &format!("hook.v1.response.message_received.{correlation_id}"),
            &serde_json::json!({ "additional_context": context }),
        )

View on GitHub (pinned to f6f22024fb)