Hmbown/CodeWhale · error
responses JSON body
Error message
responses JSON body
What it means
This `expect` panics when `serde_json::from_slice` cannot parse the recorded Responses request body, aborting with 'responses JSON body' at crates/tui/src/client.rs:7920. It verifies the client sent a valid JSON object to the `{base}/responses` endpoint; unparseable bytes mean the request serialization is broken or the recorded body is not what the client sent.
Solutions
- Dump `String::from_utf8_lossy(&requests[0].body)` to inspect the raw payload
- Confirm the client serializes the Responses payload as one JSON object with `model` and `input` fields
- Rule out compression or chunked-recording artifacts in the mock
- Fix the serializer so the body parses cleanly
Example fix
// before
let body: Value = serde_json::from_slice(&requests[0].body).expect("responses JSON body");
// after
let raw = String::from_utf8_lossy(&requests[0].body).to_string();
let body: Value = serde_json::from_str(&raw)
.unwrap_or_else(|e| panic!("responses JSON body ({e}): {raw}")); Defensive patterns
Strategy: validation
Validate before calling
let raw = String::from_utf8_lossy(&bytes);
let v: serde_json::Value = serde_json::from_str(&raw)
.expect("responses request body must be valid JSON");
assert!(v.get("input").is_some()); Type guard
fn as_json(bytes: &[u8]) -> Option<serde_json::Value> {
serde_json::from_slice(bytes).ok()
} Try / catch
let body: Value = serde_json::from_slice(&bytes)
.unwrap_or_else(|e| panic!("responses JSON body: {e}; raw: {}", String::from_utf8_lossy(&bytes))); Prevention
- Assert body shape (input present, messages absent) alongside parseability
- Dump raw bytes in the panic message for fast diagnosis
- Keep streaming recorders byte-faithful (no recompression)
When it happens
Trigger: Calling `serde_json::from_slice(&requests[0].body)` on a Responses-format request whose body is empty, truncated, SSE-encoded, or otherwise not a single JSON object.
Common situations: The Responses serializer emits an `input` array but a bug concatenates fragments; the mock records chunked/stream bytes as-is; a middleware rewrites or compresses the body before recording.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
Related errors
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/363324edf0d6dd49.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/client.rs:7920
offerings,
});
let mut client = codewhale_client(&server, "openai/gpt-5.6");
assert_eq!(client.wire_format, WireFormat::Responses);
client.retry.enabled = false;
let response = client
.create_message(minimal_zen_request("openai/gpt-5.6"))
.await
.expect("Codewhale responses request should succeed");
// Responses usage arrives on the terminal `response.completed` event —
// the dialect's equivalent of chat's `stream_options.include_usage`.
assert_eq!(response.usage.input_tokens, 3);
assert_eq!(response.usage.output_tokens, 1);
let requests = server.received_requests().await.expect("recorded request");
assert_eq!(requests.len(), 1);
assert_codewhale_bearer(&requests[0]);
let body: Value = serde_json::from_slice(&requests[0].body).expect("responses JSON body");
assert_eq!(
body.get("model").and_then(Value::as_str),
Some("openai/gpt-5.6")
);
assert!(body.get("input").is_some(), "Responses body: {body}");
assert!(body.get("messages").is_none(), "Responses body: {body}");
crate::provider_catalog_live::reset_cache_for_test();
crate::provider_lake::clear_live_snapshot();
}
fn opencode_zen_client(server: &MockServer, model: &str) -> CodewhaleClient {
let config = Config {
provider: Some("opencode-zen".to_string()),
providers: Some(ProvidersConfig {
opencode_zen: ProviderConfig {
api_key: Some("zen-test-key".to_string()),
base_url: Some(server.uri()),View on GitHub (pinned to 73e0f67d83)