zeroclaw-labs/zeroclaw · error · anyhow::Error

Schema missing required 'type' field

Error message

Schema missing required 'type' field

What it means

The Anthropic provider serializes its internally-constructed NativeChatRequest with serde_json::to_value before entering the async stream, so the request body is owned and 'static across the await boundary. to_value on this plain derived struct (strings, Options, Vecs, numbers) is infallible in practice; the expect marks that invariant. A failure would mean a field type whose Serialize impl errors — for example a map with non-string keys or a custom serializer with error paths added later — not anything about the request content or credentials.

Solutions

  1. If hit after modifying the request struct, bisect the recently added fields and check their Serialize impls.
  2. Add a unit test that builds a representative NativeChatRequest for every code path and asserts serde_json::to_value succeeds.
  3. As a maintainer, replace the expect with map_err into the provider's error stream so a serialization bug degrades to a per-request error instead of a panic.

Example fix

// before
let body = serde_json::to_value(&native_request)
    .expect("NativeChatRequest should serialize to JSON");

// after
let body = match serde_json::to_value(&native_request) {
    Ok(v) => v,
    Err(e) => {
        return stream::once(async move {
            Err(provider_error(format!("failed to serialize chat request: {e}")))
        })
    }
};
Defensive patterns

Strategy: validation

Validate before calling

// Pin the invariant the expect asserts, in your test suite:
#[test]
fn native_request_serializes_for_all_paths() {
    for req in representative_native_requests() {
        assert!(serde_json::to_value(&req).is_ok(),
            "NativeChatRequest failed to serialize: {req:?}");
    }
}

Prevention

When it happens

Trigger: Not reachable through configuration or user input with the current struct shape; it would surface only after a code change adds a field whose Serialize can fail, or a manual Serialize impl with error paths on NativeChatRequest.

Common situations: Extending NativeChatRequest with an exotic key type (non-string map keys), an untagged enum edge case, or a custom serializer; virtually never seen from configuration or network conditions.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/3ca041f89f886bcb. Report an issue: GitHub.

Appendix: source

Thrown at crates/zeroclaw-api/src/schema.rs:206

            Self::extract_defs(obj)
        } else {
            HashMap::new()
        };

        Self::clean_with_defs(schema, &defs, strategy, &mut HashSet::new())
    }

    /// Validate that a schema is suitable for LLM tool calling.
    ///
    /// Returns an error if the schema is invalid or missing required fields.
    pub fn validate(schema: &Value) -> anyhow::Result<()> {
        let obj = schema
            .as_object()
            .ok_or_else(|| anyhow::Error::msg("Schema must be an object"))?;

        // Must have 'type' field
        if !obj.contains_key("type") {
            anyhow::bail!("Schema missing required 'type' field");
        }

        // If type is 'object', should have 'properties'
        if let Some(Value::String(t)) = obj.get("type")
            && t == "object"
            && !obj.contains_key("properties")
        {
            eprintln!("warn: Object schema without 'properties' field may cause issues");
        }

        Ok(())
    }

    // --------------------------------------------------------------------
    // Internal implementation
    // --------------------------------------------------------------------

    /// Extract $defs and definitions into a flat map for reference resolution.

View on GitHub (pinned to 88bb9c8533)