{"record":{"id":"3070e7f91636bc65","repo":"BigPizzaV3/CodexPlusPlus","slug":"responses-utf-8-error","errorCode":null,"errorMessage":"Responses 请求体不是 UTF-8：{error}","messagePattern":"Responses 请求体不是 UTF-8：(.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/launcher.rs","lineNumber":1307,"sourceCode":") -> anyhow::Result<String> {\n    let encoding = content_encoding.unwrap_or_default().trim();\n    let decoded = if encoding.is_empty() || encoding.eq_ignore_ascii_case(\"identity\") {\n        body.to_vec()\n    } else if encoding.eq_ignore_ascii_case(\"zstd\") {\n        let decoder = zstd::stream::read::Decoder::new(std::io::Cursor::new(body))?;\n        let mut limited = decoder.take((MAX_HTTP_BODY_BYTES + 1) as u64);\n        let mut decoded = Vec::new();\n        limited.read_to_end(&mut decoded)?;\n        if decoded.len() > MAX_HTTP_BODY_BYTES {\n            anyhow::bail!(\"解压后的请求体超过大小限制\");\n        }\n        decoded\n    } else {\n        anyhow::bail!(\"不支持的 Content-Encoding：{encoding}\");\n    };\n\n    String::from_utf8(decoded)\n        .map_err(|error| anyhow::anyhow!(\"Responses 请求体不是 UTF-8：{error}\"))\n}\n\nfn overlay_image_response() -> (String, Vec<u8>, String, &'static str) {\n    let not_found = || {\n        (\n            \"404 Not Found\".to_string(),\n            serde_json::to_vec(&serde_json::json!({\n                \"status\": \"failed\",\n                \"message\": \"图片覆盖层未启用或图片不可用\"\n            }))\n            .unwrap_or_default(),\n            \"application/json; charset=utf-8\".to_string(),\n            \"helper.overlay_image_not_found\",\n        )\n    };\n    let settings = SettingsStore::default().load().unwrap_or_default();\n    if !settings.codex_app_image_overlay_enabled {\n        return not_found();","sourceCodeStart":1289,"sourceCodeEnd":1325,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/launcher.rs#L1289-L1325","documentation":"After decoding (identity or zstd), decode_protocol_proxy_request_body validates UTF-8 with String::from_utf8 and maps any error to this message (crates/codex-plus-core/src/launcher.rs:1306-1307). The Responses pipeline treats request bodies as JSON text, so a body containing invalid UTF-8 sequences — binary data, wrong-charset text, or a body corrupted/truncated mid-multibyte-character — is rejected before JSON parsing.","triggerScenarios":"A request to the local proxy Responses endpoint whose (decompressed) bytes are not valid UTF-8: a client sending binary in the body, a mis-declared Content-Encoding that makes zstd 'decode' plain bytes into garbage, or truncated uploads that cut a multi-byte UTF-8 sequence in half.","commonSituations":"Content-Encoding header lying about the body (labeling plain bytes as zstd or vice versa), producing mojibake that fails UTF-8; clients serializing with a non-UTF-8 legacy charset; network truncation from aggressive timeouts; test fixtures generated from raw byte slices.","solutions":["Verify the Content-Encoding actually matches the body bytes — a mismatched zstd label on plain data decodes to garbage that fails this check","Send genuine UTF-8 JSON: serialize with serde_json (always UTF-8) instead of hand-building strings in a legacy codepage","If the body is legitimately binary, it does not belong on the Responses text endpoint — move it to a binary upload path","Reproduce locally: String::from_utf8(body) in a scratch test to see the exact invalid byte offset reported in the error"],"exampleFix":"// before: legacy-codepage bytes\nlet body = encode_cp1252(text); // not UTF-8\npost(\"/responses\", body, None).await?; // bail: 请求体不是 UTF-8\n\n// after: UTF-8 JSON\nlet body = serde_json::to_vec(&payload)?; // guaranteed UTF-8\npost(\"/responses\", body, None).await?;","handlingStrategy":"validation","validationCode":"// Client side: guarantee UTF-8 before sending\nlet body_str = String::from_utf8(body.clone()).map_err(|e| anyhow!(\"body not UTF-8: {e}\"))?;\n// or simply always serialize with serde_json, which emits UTF-8","typeGuard":"fn body_is_utf8(bytes: &[u8]) -> bool {\n    std::str::from_utf8(bytes).is_ok()\n}","tryCatchPattern":"match decode_protocol_proxy_request_body(&body, encoding).await {\n    Err(e) if e.to_string().contains(\"不是 UTF-8\") => {\n        respond_400_with_detail(\"request body must be UTF-8 JSON\").await\n    }\n    rest => rest,\n}","preventionTips":["Always serialize request bodies with serde_json (guaranteed UTF-8) rather than manual string building","Double-check Content-Encoding matches the actual body bytes — mismatched zstd labels decode to garbage that fails UTF-8","Avoid truncating request bodies (timeouts, partial reads) which can split multi-byte characters"],"tags":["rust","http-proxy","utf-8","encoding","responses-api","validation"],"backgroundTag":"invalid-utf8-body","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}