BigPizzaV3/CodexPlusPlus · error
chat response choices is empty
Error message
chat response choices is empty
What it means
Thrown by chat_completion_to_response_with_context in crates/codex-plus-core/src/protocol_proxy.rs when the upstream Chat Completions body has a choices field and it is an array, but the array is empty (choices.first() returns None). The converter takes choices[0] to build the Responses output items, so an empty array means there is no completion to translate. Empty choices usually accompany load-shedding responses, content-filter short-circuits, or gateways that return 200 with an empty payload.
Source
Thrown at crates/codex-plus-core/src/protocol_proxy.rs:243
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,
));
let mut response = json!({View on GitHub (pinned to 1f431ae49b)
Solutions
- Inspect the full body — empty choices often comes with an error or usage object that names the real cause
- Retry the request: empty choices from load balancers is frequently transient (the proxy already has failover candidates for this)
- Add the upstream to the aggregate relay's member list so failover picks another provider when this one returns empty choices
- If it reproduces deterministically for one prompt, test the same payload directly against the upstream to confirm it is a provider-side filter
Example fix
// before
let choice = choices.first().ok_or_else(|| anyhow::anyhow!("chat response choices is empty"))?;
// after: retry with failover instead of hard-failing
let Some(choice) = choices.first() else {
crate::relay_rotation::record_relay_request_failure(&settings);
continue; // try next candidate relay
}; Defensive patterns
Strategy: retry
Validate before calling
if matches!(body.get("choices"), Some(Value::Array(a)) if a.is_empty()) {
// treat as transient upstream failure: rotate to next relay instead of converting
} Type guard
fn has_non_empty_choices(body: &Value) -> bool {
matches!(body.get("choices"), Some(Value::Array(a)) if !a.is_empty())
} Try / catch
match chat_completion_to_response_with_request(body, &request) {
Ok(v) => v,
Err(e) if e.to_string().contains("choices is empty") => retry_next_candidate().await?,
Err(e) => return Err(e),
} Prevention
- Add multiple member relays to the aggregate so empty-choice responses fail over
- Monitor diagnostic log protocol_proxy.upstream_response for members that frequently return empty choices and retire them
When it happens
Trigger: Upstream returns {"choices": [], ...} under heavy load or rate limiting; a content moderation layer strips the only choice and leaves an empty array; a streaming-aggregating proxy emits a final aggregate object with empty choices.
Common situations: Overcrowded third-party relay pools returning empty completions; upstream returning empty choices when the prompt trips a filter; partial upstream outages where the gateway still answers 200.
Related errors
- chat response missing choices
- chat response choice missing message
- 上游没有返回可用模型
- 当前中转未启用 Chat Completions 协议代理
- Chat Completions 上游 Base URL 不能为空
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16).
Data as JSON: /api/errors/ab4d95aba09870fb.
Report an issue: GitHub.