Skip to content

[Bug] DeepSeek V4 Flash Responses route stalls after tool calls #875

Description

@Michael-Han0608

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_output or 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

  1. Use OpenCodex with the official DeepSeek API and select deepseek/deepseek-v4-flash.
  2. Ensure the model is routed through the native Responses adapter.
  3. Start a turn that causes the model to return one or more tool calls.
  4. Observe that the tool calls may complete, but no corresponding function_call_output or follow-up request is sent for the same conversation, and no final assistant answer appears.
  5. A plain-text turn without tools can complete normally. DeepSeek V4 Pro through the openai-chat route 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 359
  • ocx-msbdeyhv-1v: HTTP 200, output tokens 401, reasoning tokens 215
  • ocx-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

Activity

  1. github-actions commented on Aug 2, 2026

    @github-actions
    Contributor

    Automated translation bookkeeping — detected language: unknown.

  2. github-actions commented on Aug 2, 2026

    @github-actions
    Contributor

    Issue reopened

    The report now contains the information required by the automated check. Thanks for updating it.

  3. Michael-Han0608 commented on Aug 2, 2026

    @Michael-Han0608
    Author

    Reproduced again on OpenCodex v2.10.0. The behavior appears unchanged from the original report:

    • plain-text turns may complete normally;
    • when the model emits tool calls, the calls can be generated and executed;
    • the upstream DeepSeek Responses request still returns HTTP 200 with non-zero output/reasoning tokens;
    • no corresponding function_call_output or follow-up Responses request is observed for the same turn;
    • Codex App therefore receives no final visible answer and the turn appears to stall.

    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-responses as the default route and verified a comparatively simple tool loop. The failure here still appears with longer or multi-tool turns.

  4. lidge-jun commented on Aug 2, 2026

    @lidge-jun
    Owner

    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 then sanitizeReasoningInputContent() (src/adapters/openai-responses.ts:35) blanked every non-empty reasoning item's content to [] — 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-level preserveResponsesReasoningContent capability (registry default on for DeepSeek; ocxr1 proxy envelopes are still stripped), with regression coverage in tests/deepseek-reasoning-replay.test.ts.

    Ruled out as the cause: service_tier. DeepSeek's official Responses docs list service_tier as 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.

  5. lidge-jun commented on Aug 2, 2026

    @lidge-jun
    Owner

    Both halves fixed in #892. (1) Reasoning replay: the passthrough sanitizer no longer blanks reasoning content for DeepSeek — the registry seeds preserveResponsesReasoningContent: true for 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.

  6. Michael-Han0608 commented on Aug 3, 2026

    @Michael-Han0608
    Author

    I tested the codex/wt3-exec branch at commit 0b30283b6db205a17b09682632d2f731ac1f1e14.

    Using deepseek/deepseek-v4-flash in 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.

  7. Ingwannu commented on Aug 3, 2026

    @Ingwannu
    Owner

    Reopening because the reporter added decisive post-closure evidence: the candidate branch still completes the initial DeepSeek Responses request but sends no function_call_output continuation 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.

  8. 11 remaining items

  9. added a commit that references this issue on Aug 5, 2026
  10. lidge-jun commented on Aug 5, 2026

    @lidge-jun
    Owner

    Fixed on dev in #1026 (merged as c1e9a204b).

    The bounded-JSON reliability policy was gated on inboundTransport === "websocket", so ordinary HTTP kept requesting a stream — exactly the websockets:false, bodyKind:sse reproduction 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, and main is at v2.10.0. It ships in the next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingproviderProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reportsstreamingSSE, WebSocket, terminal stream framestoolstool_calls, MCP, web-search / sidecar tools

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions