{"record":{"id":"c15974f970628308","repo":"BigPizzaV3/CodexPlusPlus","slug":"audio-transcriptions-content-type","errorCode":null,"errorMessage":"Audio transcriptions 请求缺少 Content-Type","messagePattern":"Audio transcriptions 请求缺少 Content-Type","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"crates/codex-plus-core/src/protocol_proxy.rs","lineNumber":815,"sourceCode":"        status_code,\n        is_stream: false,\n        content_type,\n        wire_api: UpstreamWireApi::Responses,\n        response: upstream,\n    })\n}\n\npub async fn open_audio_transcriptions_proxy_request(\n    body: &[u8],\n    content_type: &str,\n    original_user_agent: Option<&str>,\n) -> anyhow::Result<UpstreamProxyResponse> {\n    let settings = SettingsStore::default().load().unwrap_or_default();\n    let relay = crate::relay_rotation::select_relay_for_probe(&settings)?;\n    validate_upstream(&relay)?;\n    let content_type = content_type.trim();\n    if content_type.is_empty() {\n        anyhow::bail!(\"Audio transcriptions 请求缺少 Content-Type\");\n    }\n\n    let endpoint = audio_transcriptions_url(&relay.base_url);\n    let _ = crate::diagnostic_log::append_diagnostic_log(\n        \"protocol_proxy.audio_transcriptions_request\",\n        json!({\n            \"relayId\": relay.id,\n            \"relayName\": relay.name,\n            \"endpoint\": endpoint,\n            \"wireApi\": UpstreamWireApi::AudioTranscriptions,\n            \"bodyBytes\": body.len()\n        }),\n    );\n    let upstream = send_upstream_request(\n        crate::http_client::proxied_client(&effective_user_agent(\n            &relay.user_agent,\n            original_user_agent,\n        ))?","sourceCodeStart":797,"sourceCodeEnd":833,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/protocol_proxy.rs#L797-L833","documentation":"Thrown by open_audio_transcriptions_proxy_request in crates/codex-plus-core/src/protocol_proxy.rs when the incoming audio transcriptions request has an empty Content-Type header (after trimming). The proxy must forward the original multipart boundary to the upstream verbatim — it re-sends the raw body with .header(CONTENT_TYPE, content_type) — so without a Content-Type (normally multipart/form-data; boundary=...) the upstream cannot parse the multipart audio upload.","triggerScenarios":"Calling the /v1/audio/transcriptions proxy path with a client that omitted the Content-Type header; an HTTP client set to strip headers, or code building the multipart body manually and forgetting to set the header; a proxy/framework in front dropping the header.","commonSituations":"Custom scripts posting audio without multipart tooling; curl invocations missing -H 'Content-Type: multipart/form-data; boundary=...'; middleware that buffers the body and drops the original Content-Type; testing tools that default to no content type.","solutions":["Set Content-Type on the client request to the multipart type including the exact boundary used to encode the body, e.g. multipart/form-data; boundary=X","Prefer a proper multipart helper (reqwest::blocking::multipart, curl -F) so the boundary in the header matches the body","Verify no intermediate proxy strips the header before the request reaches this endpoint"],"exampleFix":"# before\ncurl -X POST http://127.0.0.1:8137/v1/audio/transcriptions --data-binary @audio.mp3\n\n# after\ncurl -X POST http://127.0.0.1:8137/v1/audio/transcriptions -F file=@audio.mp3 -F model=whisper-1","handlingStrategy":"validation","validationCode":"let content_type = request\n    .headers()\n    .get(reqwest::header::CONTENT_TYPE)\n    .and_then(|v| v.to_str().ok())\n    .unwrap_or(\"\")\n    .trim()\n    .to_string();\nif content_type.is_empty() {\n    return bad_request(\"multipart Content-Type with boundary is required\");\n}","typeGuard":"fn has_multipart_content_type(ct: &str) -> bool {\n    ct.starts_with(\"multipart/form-data\") && ct.contains(\"boundary=\")\n}","tryCatchPattern":null,"preventionTips":["Always build audio uploads with a multipart helper so the boundary matches the header","Never strip Content-Type in fronting proxies for the transcriptions endpoint","Smoke-test audio proxying once after deploying any middleware in front of the proxy"],"tags":["audio-transcriptions","multipart","http-headers","content-type"],"backgroundTag":"missing-content-type-header","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}