BigPizzaV3/CodexPlusPlus · error
chat response missing choices
Error message
chat response missing choices
What it means
Thrown by chat_completion_to_response_with_context in crates/codex-plus-core/src/protocol_proxy.rs while converting an upstream Chat Completions JSON body into the Responses API shape used by codex. The converter requires body.choices to exist and be an array; if choices is absent or not an array, conversion aborts. In practice the upstream returned a 2xx body that is not a Chat Completions response — most often an error envelope like {"error": ...}, a gateway HTML page, or a vendor-specific schema.
Source
Thrown at crates/codex-plus-core/src/protocol_proxy.rs:240
}
pub fn chat_completion_to_response_with_request(
body: Value,
original_request: &Value,
) -> anyhow::Result<Value> {
let context = build_codex_tool_context(original_request.get("tools"));
chat_completion_to_response_with_context(body, &context, Some(original_request))
}
fn chat_completion_to_response_with_context(
body: Value,
tool_context: &CodexToolContext,
original_request: Option<&Value>,
) -> anyhow::Result<Value> {
let choices = body
.get("choices")
.and_then(Value::as_array)
.ok_or_else(|| anyhow::anyhow!("chat response missing choices"))?;
let choice = choices
.first()
.ok_or_else(|| anyhow::anyhow!("chat response choices is empty"))?;
let message = choice
.get("message")
.ok_or_else(|| anyhow::anyhow!("chat response choice missing message"))?;
let response_id = response_id_from_chat_id(body.get("id").and_then(Value::as_str));
let mut output = Vec::new();
if let Some(reasoning) = chat_reasoning_to_response_output_item(message, &response_id) {
output.push(reasoning);
}
if let Some(message) = chat_message_to_response_output_item(message, &response_id) {
output.push(message);
}
output.extend(chat_tool_calls_to_response_output_items(
message,
tool_context,View on GitHub (pinned to 1f431ae49b)
Solutions
- Log or dump the raw upstream body — the real problem (usually an embedded error message) is inside the JSON that lacks choices
- Check the upstream HTTP status and body.error field before attempting chat_completion_to_response conversion
- Verify the relay's base_url actually serves OpenAI-compatible /v1/chat/completions (curl it directly with the same key)
- If the upstream only speaks the Responses API, switch the relay profile protocol to Responses instead of ChatCompletions
Example fix
// before
let response = chat_completion_to_response_with_request(body, &request)?;
// after
if body.get("choices").and_then(Value::as_array).is_none() {
anyhow::bail!("upstream returned non-completion body: {}", body);
}
let response = chat_completion_to_response_with_request(body, &request)?; Defensive patterns
Strategy: type-guard
Validate before calling
fn upstream_ok_for_conversion(body: &Value) -> bool {
body.get("choices").and_then(Value::as_array).is_some_and(|c| !c.is_empty())
&& body.get("error").is_none()
} Type guard
fn is_chat_completion_body(body: &Value) -> bool {
matches!(body.get("choices"), Some(Value::Array(choices)) if !choices.is_empty()
&& choices[0].get("message").is_some())
} Try / catch
let response = chat_completion_to_response_with_request(body.clone(), &request)
.with_context(|| format!("upstream returned non-standard body: {body}"))?; Prevention
- Check body.error and the choices array before converting
- curl the relay's /v1/chat/completions once with a trivial prompt to confirm the schema before adding it as a ChatCompletions provider
- Keep a failover member configured so one non-compliant upstream does not kill the request
When it happens
Trigger: Relay whose /chat/completions endpoint returns 200 with an error object instead of a completion; upstream gateway (Cloudflare, nginx) returning a JSON status body without choices; pointing the ChatCompletions relay at a Responses-only or legacy completions endpoint whose response schema has no choices array.
Common situations: Third-party API relays that wrap errors in 200 responses; base_url pointing at the wrong path so the upstream answers with a generic JSON document; upstream model overloaded and returning a non-standard body; version drift where the vendor renamed response fields.
Related errors
- chat response choices is empty
- chat response choice missing message
- Chat Completions 协议暂不支持 Responses compact 请求
- 解压后的请求体超过大小限制
- Responses 请求体不是 UTF-8:{error}
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16).
Data as JSON: /api/errors/25119eec88c6ec2f.
Report an issue: GitHub.