Hmbown/CodeWhale · error
Responses JSON body
Error message
Responses JSON body
What it means
Test-side `.expect("Responses JSON body")` panic from `serde_json::from_slice`. The captured request body is not valid JSON, so the test cannot inspect the Responses payload (`model`, `input`, absence of `messages`). This means the client sent a non-JSON body, a truncated body, or an SSE/other content type to the Responses endpoint.
Solutions
- Dump `&requests[0].body` bytes to see what was actually sent before parsing.
- Confirm the Responses wire encoder serializes the request envelope with `serde_json::to_vec`/`to_string`.
- Check the request `Content-Type` is `application/json` and no compression middleware altered the body.
- Fix the encoder in crates/tui/src/client.rs request building, not the test assertion.
Example fix
// before
let body: Value = serde_json::from_slice(&requests[0].body).expect("Responses JSON body");
// after (diagnosis)
let body: Value = serde_json::from_slice(&requests[0].body)
.unwrap_or_else(|e| panic!("Responses JSON body: {e}; raw={:?}", String::from_utf8_lossy(&requests[0].body))); Defensive patterns
Strategy: validation
Validate before calling
// Rust (pre-validate the captured body)
let raw = std::str::from_utf8(&requests[0].body).expect("body is UTF-8");
assert!(raw.trim_start().starts_with('{'), "Responses request body must be a JSON object: {raw}"); Type guard
fn json_object(body: &[u8]) -> Option<serde_json::Value> {
serde_json::from_slice(body).ok().filter(|v| v.is_object())
} Try / catch
let body: Value = serde_json::from_slice(&requests[0].body)
.unwrap_or_else(|e| panic!("Responses JSON body: {e}; raw={}", String::from_utf8_lossy(&requests[0].body))); Prevention
- Keep request serialization behind serde_json only; never hand-build bodies.
- Assert `Content-Type: application/json` on captured requests.
- Dump the raw body in expect messages for fast diagnosis.
- Watch for middleware that gzips or otherwise transforms captured bodies.
When it happens
Trigger: Running `opencode_zen_responses_request_uses_responses_route_without_oauth_headers` when `create_message_stream` serializes the Responses request as something other than JSON, the body got compressed/chunked unexpectedly, or the mock captured a body from a different request shape.
Common situations: Changing request serialization to a form-encoded or binary format by mistake; sending the streaming body instead of the JSON request envelope; accidental double-encoding or BOM prefix.
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
- Absolute path should not warn
- ApplyPatchPreflight should serialize
- bundled model catalog must parse
- child assignment
- child-local search
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/390b79eb6384e0aa.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/client.rs:8140
)
.expect(1)
.mount(&server)
.await;
let client = opencode_zen_client(&server, "gpt-5.5");
assert_eq!(client.wire_format, WireFormat::Responses);
let mut stream = client
.create_message_stream(minimal_zen_request("gpt-5.5"))
.await
.expect("Zen Responses request should start");
while let Some(event) = stream.next().await {
event.expect("Zen Responses stream event");
}
let requests = server.received_requests().await.expect("recorded request");
assert_eq!(requests.len(), 1);
assert_zen_bearer_without_codex_headers(&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("gpt-5.5"));
assert!(body.get("input").is_some(), "Responses body: {body}");
assert!(body.get("messages").is_none(), "Responses body: {body}");
}
#[tokio::test]
async fn opencode_zen_messages_request_shape_uses_api_key_anthropic_route() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/v1/messages"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"id": "msg_zen",
"type": "message",
"role": "assistant",
"content": [{"type": "text", "text": "ok"}],
"model": "claude-sonnet-4-6",
"stop_reason": "end_turn",
"stop_sequence": null,View on GitHub (pinned to 73e0f67d83)