Repository navigation
[Bug] DeepSeek V4 Flash Responses route stalls after tool calls #875
Description
Thanks for the report. Include a minimal reproduction and exact environment details so we can confirm and fix the problem quickly.
Client or integration
Codex App
Area
Tools / MCP / web search
Summary
DeepSeek V4 Flash requests routed through the native Responses adapter can stall after tool calls. The upstream request returns HTTP 200 and non-zero output/reasoning tokens, but OpenCodex does not continue with the corresponding tool outputs, so no final visible answer is produced.
Plain-text turns without tools can complete normally. DeepSeek V4 Pro through the openai-chat route works normally under the same local setup.
Update: reproduced on OpenCodex 2.10.0
The same behavior remains reproducible after upgrading to OpenCodex 2.10.0:
- plain-text turns may complete normally;
- tool calls can be generated and executed;
- the upstream DeepSeek Responses request returns HTTP 200 with non-zero output/reasoning tokens;
- no corresponding
function_call_outputor follow-up Responses request is observed for the same turn; - Codex App receives no final visible answer and the turn stalls.
The v2.10.0 routing change does not appear to address this continuation failure. Codex-originated requests still use the native Responses path, so the remaining defect appears to be in the Responses tool-call continuation, event handoff, or reasoning-history replay rather than initial model routing.
Reproduction
- Use OpenCodex with the official DeepSeek API and select
deepseek/deepseek-v4-flash. - Ensure the model is routed through the native Responses adapter.
- Start a turn that causes the model to return one or more tool calls.
- Observe that the tool calls may complete, but no corresponding
function_call_outputor follow-up request is sent for the same conversation, and no final assistant answer appears. - A plain-text turn without tools can complete normally. DeepSeek V4 Pro through the
openai-chatroute does not reproduce the problem.
The exact trigger is not yet confirmed; it may involve the native Responses event handoff, multiple tool calls, or reasoning-history replay.
Version
Originally reproduced on OpenCodex 2.9.1; reproduced again on OpenCodex 2.10.0.
Operating system
macOS
Provider and model
Official DeepSeek API / deepseek/deepseek-v4-flash
Logs or error output
Relevant local usage records:
ocx-msbd27ow-17: HTTP 200, output tokens 568, reasoning tokens 359ocx-msbdeyhv-1v: HTTP 200, output tokens 401, reasoning tokens 215ocx-msbdfnwj-1x: HTTP 200, output tokens 113, reasoning tokens 36
The first two responses contained two completed exec_command function calls, but no corresponding function_call_output or follow-up request was observed for the same conversation.
service_tier was default; no priority, 400, or 502 error was recorded.
Screenshots and supporting files
The issue was reproduced three times after a long processing period with no visible output. Local usage records are available above.
Redacted configuration
The relevant setup uses the official DeepSeek provider, with deepseek/deepseek-v4-flash routed through the native Responses adapter. No secrets or credentials are included.
The current Responses adapter appears to sanitize reasoning.content for all Responses providers. DeepSeek's Responses API supports plain-text reasoning_text input, so this may also affect subsequent tool-call replay. This observation is not yet confirmed as the primary trigger.
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
References
- providers: route DeepSeek V4 Flash through native Responses API by default #807 — route DeepSeek V4 Flash through the native Responses API
- fix(providers): default DeepSeek V4 Flash to Responses #808 — default DeepSeek V4 Flash to Responses
- fix(responses): gate service tiers by provider capability #860 — gate service tiers by provider capability
- DeepSeek Responses API compatibility: https://api-docs.deepseek.com/guides/responses_api
Activity
github-actions commentedon Aug 2, 2026
Automated translation bookkeeping — detected language: unknown.
github-actions commentedon Aug 2, 2026
Issue reopened
The report now contains the information required by the automated check. Thanks for updating it.
Michael-Han0608 commentedon Aug 2, 2026
Reproduced again on OpenCodex v2.10.0. The behavior appears unchanged from the original report:
function_call_outputor follow-up Responses request is observed for the same turn;The v2.10.0 routing change does not appear to address this continuation failure. It keeps Codex-originated requests on the native Responses path, while Chat/Anthropic-originated requests use the provider-wide Chat route. The remaining defect therefore seems specific to the native Responses tool-call continuation or reasoning-history replay, rather than model selection or initial wire routing.
For reference, #808 only established
deepseek-v4-flash→openai-responsesas the default route and verified a comparatively simple tool loop. The failure here still appears with longer or multi-tool turns.lidge-jun commentedon Aug 2, 2026
Triage verdict (wt3 lane, branch codex/wt3-exec)
We split this into a local defect (fixed) and an unexplained residual (still open).
Local defect — found and fixed. On continuation requests, ocx preserved reasoning items in its continuation store (
src/responses/state.ts:699,:806,:837) and through DeepSeek's stateless cleanup (src/adapters/openai-responses.ts:1003), but thensanitizeReasoningInputContent()(src/adapters/openai-responses.ts:35) blanked every non-empty reasoning item'scontentto[]— for EVERY Responses provider, although that rule exists only for the ChatGPT native backend. DeepSeek's Responses API accepts plaintext reasoning replay (its compatibility guide merges reasoning items into the adjacent assistant message), so every continuation reached DeepSeek with an emptied reasoning item. The fix scopes the sanitizer via a provider-levelpreserveResponsesReasoningContentcapability (registry default on for DeepSeek;ocxr1proxy envelopes are still stripped), with regression coverage intests/deepseek-reasoning-replay.test.ts.Ruled out as the cause:
service_tier. DeepSeek's official Responses docs listservice_tieras unsupported but state unsupported Responses parameters are silently ignored — so it cannot explain a stall. We still capability-gated it fail-closed (supportsServiceTier, PR #860's reviewed semantics) as compatibility hygiene, but that is not this bug.Residual — not explained locally. The "no follow-up HTTP request sent at all" observation cannot be produced by any ocx code path we found: the reasoning defect only fires once a follow-up request REACHES the proxy. If no request was sent, the failure is upstream of us (client/SSE handoff) or in the serving path (external stall reports for V4-Flash are on NVIDIA NIM / vLLM, not hosted api.deepseek.com).
What would close this issue: a repro against the fixed build showing whether the follow-up request now (a) is sent and succeeds — done; (b) is sent and 400s — new evidence, send us the status + body; or (c) is still never sent — that pins the residual to the client/SSE handoff, and a capture of the client-side stream would be the next evidence we need.
Leaving open pending that confirmation; the local half ships with the wt3 branch.
docs(plan): allocate 12 must-fix bugs across 5 bugfix worktrees
fix(deepseek): preserve Responses reasoning replay on continuations (l…
lidge-jun commentedon Aug 2, 2026
Both halves fixed in #892. (1) Reasoning replay: the passthrough sanitizer no longer blanks reasoning content for DeepSeek — the registry seeds
preserveResponsesReasoningContent: truefor the DeepSeek entry (src/providers/registry.ts:1001) and the adapter honors it, with regression coverage in tests/deepseek-reasoning-replay.test.ts citing this issue. (2) Codex Desktop WebSocket terminal event: WS-inbound DeepSeek V4 Flash turns are answered with bounded upstream JSON (registry-scoped hint) that the WS bridge reframes into a complete event sequence, so the turn can no longer stall waiting for a terminal event that never arrives. Note: PR #906 proposed the same proxy-half fix via a per-model list; closed as a functional duplicate of the landed provider-level flag.Michael-Han0608 commentedon Aug 3, 2026
I tested the
codex/wt3-execbranch at commit0b30283b6db205a17b09682632d2f731ac1f1e14.Using
deepseek/deepseek-v4-flashin Codex Desktop, I sent a request requiring multiple tool calls. The initial DeepSeek Responses request returned HTTP 200 with non-zero output and reasoning tokens, but the turn remained pending and never produced a final answer.I did not observe a follow-up DeepSeek Responses request carrying the tool results for the same turn.
On this tested commit, the behavior therefore matches case (c) from the triage comment: the initial request succeeds, but no follow-up request is sent and the Codex client remains waiting.
Ingwannu commentedon Aug 3, 2026
Reopening because the reporter added decisive post-closure evidence: the candidate branch still completes the initial DeepSeek Responses request but sends no
function_call_outputcontinuation request and leaves Codex Desktop pending. That means the residual described here was not fixed by the reasoning-replay and bounded-JSON changes already landed.PR #940 is related but not a complete fix for this built-in path in its current form. It adds an opt-in SSE repair, while the built-in DeepSeek preset does not enable that option and Codex App WebSocket turns use the bounded JSON-to-WebSocket path instead of the SSE rewrite. Keeping #875 as the canonical tool-loop issue so the existing reproduction and diagnosis stay together. #946 is being closed as its duplicate.
11 remaining items
Merge pull request #1026 from lidge-jun/codex/stack-04-deepseek-bound…
fix(deepseek): transport-neutral bounded-JSON policy with terminal SS…
fix(deepseek): exclude WebSocket turns from the HTTP SSE synthesis (l…
lidge-jun commentedon Aug 5, 2026
Fixed on
devin #1026 (merged asc1e9a204b).The bounded-JSON reliability policy was gated on
inboundTransport === "websocket", so ordinary HTTP kept requesting a stream — exactly thewebsockets:false, bodyKind:ssereproduction reported here. The policy is now transport-neutral: for a model that declares it, the proxy asks upstream for bounded JSON and reframes the completed response as a canonical terminal SSE sequence, with exactly one[DONE], so a streaming client commits the turn instead of hanging.It also composes with the item-ID repair rather than bypassing it, which review caught as a way this policy could silently disable that fix.
Thanks to @Michael-Han0608 (#1006) — the diagnosis and the core mechanism come from that PR.
Not in a release yet. This is on
dev, andmainis atv2.10.0. It ships in the next release.fix(deepseek): restore live upstream streaming on the Responses wire
docs(plan): allocate 12 must-fix bugs across 5 bugfix worktrees
docs(plan): split wt3 roadmap into per-bug decade docs with researche…
fix(responses): gate service_tier by provider capability, preserve De…
docs(triage): map the open bug surface and lock the stack order
fix(deepseek): transport-neutral bounded-JSON policy with terminal SS…
fix(deepseek): exclude WebSocket turns from the HTTP SSE synthesis (l…
Merge pull request lidge-jun#1026 from lidge-jun/codex/stack-04-deeps…
fix(deepseek): canonical item-id repair for UUID message/reasoning ids (
fix(deepseek): restore live upstream streaming on the Responses wire