Client or integration
Codex App (also reproduced with a direct /v1/chat/completions client)
Area
Provider adapter
Summary
The #950 fix re-attaches reasoning_content from the in-process replay cache, but that cache is bounded (64 entries / 256 KiB / 1 h TTL, see src/responses/reasoning-replay-cache.ts). When the cache misses on a long session, src/adapters/openai-chat.ts still emits an assistant tool_call history message without reasoning_content, and DeepSeek thinking mode rejects it with HTTP 400:
Provider error 400: Error from provider (Console): Upstream request failed: [invalid_request_error] The `reasoning_content` in the thinking mode must be passed back to the API.
Both affected paths have no fallback when the cache lookup is empty:
- Main assistant path (
openai-chat.ts ~line 348): if (cached.length > 0) { reasoningContent = ... } — when cached.length === 0, the message goes out bare.
- Orphan-repair path (
openai-chat.ts ~line 412): ...(cachedReasoning ? { reasoning_content: cachedReasoning } : {}) — when cachedReasoning is undefined, the synthesized assistant tool_call has no reasoning_content.
Reproduction
- Configure the opencode-zen provider with
preserveReasoningContentModels: ["deepseek-v4-flash-free"].
- Run a long multi-turn tool-calling session with DeepSeek thinking mode (
reasoning_effort=max).
- Continue a turn whose assistant tool_call history entry lost its reasoning item (compaction, lost assistant turn, or resumed thread) and whose call id is no longer in the replay cache (evicted by >64 entries, >256 KiB, or >1 h TTL).
- The request fails with HTTP 400 as above.
Verified conditions:
- A/B test with the same conversation shape: assistant tool_call message missing
reasoning_content -> 400; same shape with a minimal placeholder (" ") -> 200.
- Failures are intermittent and correlate exactly with cache misses: they appear only when
peekReasoningForCall returns nothing for the call id.
- Reproduced both after >1 h idle and after enough tool rounds to evict the entry.
- The same session is stable with providers whose upstream tolerates missing
reasoning_content (e.g. Kimi), which is why the bug is provider-specific.
Version
2.10.2 (npm @bitkyc08/opencodex); also confirmed present on current main.
Operating system
macOS (darwin, arm64)
Provider and model
opencode-zen / deepseek-v4-flash-free (OpenCode Zen Console, DeepSeek thinking mode)
Logs or error output
Provider error 400: Error from provider (Console): Upstream request failed: [invalid_request_error] The `reasoning_content` in the thinking mode must be passed back to the API.
Redacted configuration
{
"providers": {
"opencode-zen": {
"baseUrl": "https://opencode.ai/zen/v1",
"preserveReasoningContentModels": ["deepseek-v4-flash-free"]
}
}
}
Suggested fix
- When the replay cache misses for a
preserveReasoningContentModels provider, never emit a bare tool_call continuation: inject a minimal placeholder (" ") in both the main assistant path and the orphan-repair path of src/adapters/openai-chat.ts. Verified locally that DeepSeek thinking mode accepts the placeholder (both " " and "..." returned 200), so this closes the 400 deterministically.
- Longer-term: instead of relying on the bounded in-process side cache, persist the reasoning attachment with the conversation state (e.g.
responses-state) so resumed tool loops keep the original reasoning text, or make cache eviction aware of preserveReasoningContentModels call ids.
Checks
Client or integration
Codex App (also reproduced with a direct
/v1/chat/completionsclient)Area
Provider adapter
Summary
The #950 fix re-attaches
reasoning_contentfrom the in-process replay cache, but that cache is bounded (64 entries / 256 KiB / 1 h TTL, seesrc/responses/reasoning-replay-cache.ts). When the cache misses on a long session,src/adapters/openai-chat.tsstill emits an assistanttool_callhistory message withoutreasoning_content, and DeepSeek thinking mode rejects it with HTTP 400:Both affected paths have no fallback when the cache lookup is empty:
openai-chat.ts~line 348):if (cached.length > 0) { reasoningContent = ... }— whencached.length === 0, the message goes out bare.openai-chat.ts~line 412):...(cachedReasoning ? { reasoning_content: cachedReasoning } : {})— whencachedReasoningis undefined, the synthesized assistant tool_call has noreasoning_content.Reproduction
preserveReasoningContentModels: ["deepseek-v4-flash-free"].reasoning_effort=max).Verified conditions:
reasoning_content-> 400; same shape with a minimal placeholder (" ") -> 200.peekReasoningForCallreturns nothing for the call id.reasoning_content(e.g. Kimi), which is why the bug is provider-specific.Version
2.10.2 (npm
@bitkyc08/opencodex); also confirmed present on currentmain.Operating system
macOS (darwin, arm64)
Provider and model
opencode-zen/deepseek-v4-flash-free(OpenCode Zen Console, DeepSeek thinking mode)Logs or error output
Redacted configuration
{ "providers": { "opencode-zen": { "baseUrl": "https://opencode.ai/zen/v1", "preserveReasoningContentModels": ["deepseek-v4-flash-free"] } } }Suggested fix
preserveReasoningContentModelsprovider, never emit a bare tool_call continuation: inject a minimal placeholder (" ") in both the main assistant path and the orphan-repair path ofsrc/adapters/openai-chat.ts. Verified locally that DeepSeek thinking mode accepts the placeholder (both" "and"..."returned 200), so this closes the 400 deterministically.responses-state) so resumed tool loops keep the original reasoning text, or make cache eviction aware ofpreserveReasoningContentModelscall ids.Checks