Repository navigation
[Bug] preserveReasoningContentModels still emits bare tool_call continuations when the replay cache misses (long sessions) — DeepSeek thinking mode 400 #1193
Copy link
Copy link
Closed
Labels
bugSomething isn't workingSomething isn't workingproviderProvider adapters, OpenAI-compat presets, upstream API quirksProvider adapters, OpenAI-compat presets, upstream API quirkstoolstool_calls, MCP, web-search / sidecar toolstool_calls, MCP, web-search / sidecar tools
Description
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 = ... }— whencached.length === 0, the message goes out bare. - Orphan-repair path (
openai-chat.ts~line 412):...(cachedReasoning ? { reasoning_content: cachedReasoning } : {})— whencachedReasoningis undefined, the synthesized assistant tool_call has noreasoning_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
peekReasoningForCallreturns 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
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. - 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 ofpreserveReasoningContentModelscall ids.
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't workingproviderProvider adapters, OpenAI-compat presets, upstream API quirksProvider adapters, OpenAI-compat presets, upstream API quirkstoolstool_calls, MCP, web-search / sidecar toolstool_calls, MCP, web-search / sidecar tools
Activity
github-actions commentedon Aug 7, 2026
Automated translation bookkeeping — detected language: English.
gdxnpy commentedon Aug 7, 2026
Here is the exact local workaround I have been running (OpenCodex 2.10.2 / also applies to current
main): two hunks insrc/adapters/openai-chat.tsthat close this gap. When the bounded replay cache misses for apreserveReasoningContentModelsprovider, both paths inject a minimal placeholder (" ") instead of emitting a bare tool_call continuation. Verified A/B: same conversation shape missingreasoning_content-> 400, with the placeholder -> 200 (DeepSeek thinking mode also accepts"...").Notes:
providers.opencode-zen.preserveReasoningContentModels: ["deepseek-v4-flash-free"]in config; the fallback exercises only for models in that list." "placeholder is semantically "empty" to downstream compactions, so the maintainers may prefer persisting the reasoning with conversation state (e.g.responses-state) instead of a bounded side cache, as suggested above.fix(openai-chat): scope reasoning placeholder to models that require it
fix(openai-chat): inject reasoning placeholder when replay cache misses
fix(openai-chat): scope reasoning placeholder to models that require it
fix(openai-chat): inject reasoning placeholder when replay cache misses
fix(openai-chat): scope reasoning placeholder to models that require it
fix(openai-chat): gate orphan placeholder on preserve list, keep opt-…
Ingwannu commentedon Aug 8, 2026
Confirmed as a real cache-miss continuation bug. The implementation is now tracked in draft PR #1205, which covers both the normal assistant-history path and orphan-repair path while limiting placeholder fabrication to explicitly configured models. I am leaving this issue open: the PR still has a failing hygiene gate on an auth-surface allowlist, queued exact-head CI, and no independent maintainer approval. It should close only after those boundaries are reviewed and the fix merges into
dev.fix(openai-chat): inject reasoning placeholder when replay cache misses
fix(openai-chat): scope reasoning placeholder to models that require it
fix(openai-chat): gate orphan placeholder on preserve list, keep opt-…
Wibias commentedon Aug 9, 2026
Resolved on
dev.The fix developed in #1205 has been landed directly with the contributor's authorship preserved:
817192fcd73c15c528847with follow-up boundary-test coverage in:
5cd3edbThe landed implementation fixes both cache-miss continuation paths:
For models that require reasoning placeholders, a replay-cache miss now emits a minimal
reasoning_contentplaceholder instead of a bare assistanttool_callcontinuation.The final implementation also separates models that merely preserve reasoning from models that require placeholder fabrication, with explicit opt-outs for providers/models where thinking can legitimately be disabled.
The auth/config serialization boundaries introduced by that metadata were independently reviewed and covered so explicit non-empty and empty placeholder lists survive configuration handling without exposing credentials.
This resolves the DeepSeek thinking-mode 400 described here.
Closing as completed.
fix(openai-chat): inject reasoning placeholder when replay cache misses
fix(openai-chat): scope reasoning placeholder to models that require it
fix(openai-chat): gate orphan placeholder on preserve list, keep opt-…