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.
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)
Solutions
- If hit after modifying the request struct, bisect the recently added fields and check their Serialize impls.
- Add a unit test that builds a representative NativeChatRequest for every code path and asserts serde_json::to_value succeeds.
- 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
- Keep provider request DTOs to plain derive(Serialize) types
- Add serialization round-trip tests whenever touching request structs
- Avoid non-string map keys and custom Serialize impls with error paths on wire types
- Treat a panic deep inside a stream as an invariant regression, not a config or network issue
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
- needs_reassembly implies a step agent alias
- owned implies a reassembly handle
- serialize canonical content
- purge_agent not supported by this memory backend
- rename_agent not supported by this memory backend
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/3ca041f89f886bcb.
Report an issue: GitHub.