Responses API: reasoning items with content: null rejected by non-OpenAI providers like Openrouter
## Bug
When using the Responses API wire format (wire_api = "responses") with non-OpenAI providers (e.g. OpenRouter), multi-turn conversations fail because reasoning items with content: null are included in the request body. These providers reject the null field.
## Error
Invalid Responses API request: 3 validation error(s)
- body.input[3].content: value is not a valid list
- body.input[5].content: value is not a valid list
The server expects content to be omitted entirely (not set to null) when there's no reasoning text.
## Root Cause
In codex-rs/protocol/src/models.rs, the function should_serialize_reasoning_content returns false for None, which means #[serde(skip_serializing_if)] does NOT skip serialization, resulting in "content": null in the JSON output.
```rust
fn should_serialize_reasoning_content(content: &Option<Vec<ReasoningItemContent>>) -> bool {
match content {
Some(content) => !content
.iter()
.any(|c| matches!(c, ReasoningItemContent::ReasoningText { .. })),
None => false, // BUG: should be true to skip serializing null
}
}
The function name is inverted — it's used as skip_serializing_if, so returning true means "skip". None should return true (skip it) to omit the field.
Fix
- None => false,
- None => true,
Introduced In
Commit f1be7978cf (PR #2277 "Parse reasoning text content", Aug 13, 2025).
Reproduction
- Configure codex with wire_api = "responses" and a non-OpenAI provider
- Start a conversation and send multiple turns
- Second turn fails because reasoning items from the first response are sent back with content: null
Affected Versions
0.22.0 through at least 0.101.0-alpha.1
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗