{"record":{"id":"390b79eb6384e0aa","repo":"Hmbown/CodeWhale","slug":"responses-json-body-client","errorCode":null,"errorMessage":"Responses JSON body","messagePattern":"Responses JSON body","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/client.rs","lineNumber":8140,"sourceCode":"            )\n            .expect(1)\n            .mount(&server)\n            .await;\n\n        let client = opencode_zen_client(&server, \"gpt-5.5\");\n        assert_eq!(client.wire_format, WireFormat::Responses);\n        let mut stream = client\n            .create_message_stream(minimal_zen_request(\"gpt-5.5\"))\n            .await\n            .expect(\"Zen Responses request should start\");\n        while let Some(event) = stream.next().await {\n            event.expect(\"Zen Responses stream event\");\n        }\n\n        let requests = server.received_requests().await.expect(\"recorded request\");\n        assert_eq!(requests.len(), 1);\n        assert_zen_bearer_without_codex_headers(&requests[0]);\n        let body: Value = serde_json::from_slice(&requests[0].body).expect(\"Responses JSON body\");\n        assert_eq!(body.get(\"model\").and_then(Value::as_str), Some(\"gpt-5.5\"));\n        assert!(body.get(\"input\").is_some(), \"Responses body: {body}\");\n        assert!(body.get(\"messages\").is_none(), \"Responses body: {body}\");\n    }\n\n    #[tokio::test]\n    async fn opencode_zen_messages_request_shape_uses_api_key_anthropic_route() {\n        let server = MockServer::start().await;\n        Mock::given(method(\"POST\"))\n            .and(path(\"/v1/messages\"))\n            .respond_with(ResponseTemplate::new(200).set_body_json(json!({\n                \"id\": \"msg_zen\",\n                \"type\": \"message\",\n                \"role\": \"assistant\",\n                \"content\": [{\"type\": \"text\", \"text\": \"ok\"}],\n                \"model\": \"claude-sonnet-4-6\",\n                \"stop_reason\": \"end_turn\",\n                \"stop_sequence\": null,","sourceCodeStart":8122,"sourceCodeEnd":8158,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/client.rs#L8122-L8158","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet body: Value = serde_json::from_slice(&requests[0].body).expect(\"Responses JSON body\");\n// after (diagnosis)\nlet body: Value = serde_json::from_slice(&requests[0].body)\n    .unwrap_or_else(|e| panic!(\"Responses JSON body: {e}; raw={:?}\", String::from_utf8_lossy(&requests[0].body)));","handlingStrategy":"validation","validationCode":"// Rust (pre-validate the captured body)\nlet raw = std::str::from_utf8(&requests[0].body).expect(\"body is UTF-8\");\nassert!(raw.trim_start().starts_with('{'), \"Responses request body must be a JSON object: {raw}\");","typeGuard":"fn json_object(body: &[u8]) -> Option<serde_json::Value> {\n    serde_json::from_slice(body).ok().filter(|v| v.is_object())\n}","tryCatchPattern":"let body: Value = serde_json::from_slice(&requests[0].body)\n    .unwrap_or_else(|e| panic!(\"Responses JSON body: {e}; raw={}\", String::from_utf8_lossy(&requests[0].body)));","preventionTips":["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."],"tags":["test-assertion","serde-json","http-body","rust"],"backgroundTag":"json-parse-error","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}