Repository navigation
DeepSeek V4 web-search continuation drops reasoning_raw_delta and returns 502 #688
Description
Client or integration
VS Code Codex extension
Area
Provider adapters / hosted Web Search loop
Summary
DeepSeek V4 thinking-mode requests can still fail after a synthetic hosted web_search call on opencodex v2.7.42, even though ordinary tool-call history replay is covered by #61 / #58.
The direct DeepSeek provider is correctly configured with preserveReasoningContentModels for deepseek-v4-pro and deepseek-v4-flash. However, the internal Web Search loop appears to lose DeepSeek's raw reasoning when it reconstructs the assistant tool-call message for the next model iteration.
The upstream rejection is:
Provider error 400: The `reasoning_content` in the thinking mode must be passed back to the API.
opencodex surfaces this to Codex as a 502/in-stream failure, after which Codex retries the sampling request.
Reproduction
- Configure the built-in direct
deepseekprovider withdeepseek-v4-pro. - Start a fresh Codex task through the Responses endpoint.
- Ask for a research/current-information task that causes the synthetic hosted
web_searchtool to be invoked. - Let the model emit thinking/reasoning followed by the
web_searchtool call. - After the search result is injected, observe the next internal DeepSeek iteration.
This was observed after several normal shell/plan tool continuations had succeeded. The failure occurred specifically during the Web Search continuation, not on the first prompt.
Expected behavior
The assistant message replayed by the Web Search loop should include the original DeepSeek reasoning_content together with the synthetic tool_calls, allowing the next thinking-mode request to continue.
Actual behavior
The follow-up DeepSeek request is rejected with HTTP 400 because the replayed assistant tool-call message does not contain reasoning_content. opencodex records the request as:
status=502
errorCode=upstream_server_error
terminalStatus=failed
upstreamError=Provider error 400: The `reasoning_content` in the thinking mode must be passed back to the API.
The Codex client then reports:
codex_core::responses_retry: stream disconnected - retrying sampling request
Suspected root cause
The OpenAI Chat adapter emits DeepSeek reasoning chunks as reasoning_raw_delta:
But extractIterationThinking() in the Web Search loop only collects thinking_delta, thinking_signature, and redacted_thinking:
The returned value is then passed as precedingThinking when reconstructing the assistant web_search tool-call message:
For DeepSeek/OpenAI-compatible streams, extractIterationThinking() therefore returns null, so the assistant replay has the tool call but no thinking part. The normal OpenAI Chat request builder can only restore reasoning_content when an internal thinking part is present.
This looks like a path missed by the earlier #61 fix: ordinary Codex tool-call history replay works, but opencodex's internal hosted-Web-Search replay has its own event collection logic.
Suggested fix
Collect raw reasoning deltas in extractIterationThinking() as well:
if (e.type === "thinking_delta") thinking += e.thinking;
else if (e.type === "reasoning_raw_delta") thinking += e.text;
else if (e.type === "thinking_signature") signature = e.signature;
else if (e.type === "redacted_thinking") redacted.push(e.data);A regression test could feed reasoning_raw_delta followed by a synthetic web_search tool call into the loop and assert that the next OpenAI Chat request contains an assistant message with both reasoning_content and tool_calls. Existing thinking_delta/Anthropic coverage should remain unchanged.
Version
2.7.42
Operating system
macOS arm64
Provider and model
Built-in direct deepseek provider, deepseek/deepseek-v4-pro, thinking mode enabled
Logs or error output
See the redacted error excerpts above. No credentials, request bodies, local paths, account identifiers, or conversation IDs are included.
Redacted configuration
{
"adapter": "openai-chat",
"baseUrl": "https://api.deepseek.com",
"defaultModel": "deepseek-v4-pro",
"preserveReasoningContentModels": [
"deepseek-v4-pro",
"deepseek-v4-flash"
]
}Checks
- I searched existing issues and documentation.
- I reproduced this on the current v2.7.42 release.
- I removed secrets, tokens, account details, request credentials, local paths, and personal data.
- I checked Reconnecting error with deepseek api #58 / Preserve DeepSeek V4 thinking history #61; this report concerns the separate internal hosted-Web-Search replay path.
Activity
github-actions commentedon Jul 29, 2026
Automated translation bookkeeping — detected language: English.
fix(web-search): carry raw reasoning into the replayed web_search turn
chore(devlog): advance the plan submodule to f76fc29
lidge-jun commentedon Jul 30, 2026
Fixed on
devin 89931a1.Root cause.
extractIterationThinking()insrc/web-search/loop.tsonly accumulatedthinking_delta. DeepSeek V4 and other OpenAI-compatible providers emitreasoning_raw_deltainstead (src/adapters/openai-chat.ts:743), so the helper returnednulland the replayed assistant turn went upstream as a bare tool call with noreasoning_contentbeside it.The serializer side already worked —
openai-chat.ts:185emitsreasoning_contentfrom thinking parts for models inpreserveReasoningContentModels, and DeepSeek V4 is registered there. It was simply never handed a part.One correction to the report. The 502 is our own representation, not DeepSeek's status. Upstream rejects with 400; the loop drops
LoopError.statusonce the SSE is already open, and the message-based inference then falls back to 502. Your reproduction is resolved because the 400 no longer happens, but status propagation for unrelated later provider failures in this loop is still a separate gap and is not fixed here.Fix. Raw reasoning is carried as a separate unsigned thinking part. It must not join a signed block: a signature authenticates the exact text it closed, so merging would invalidate it and, worse, dress unrelated chain-of-thought as an authenticated provider block. Unsigned parts are skipped by the Anthropic serializer and serialized as
reasoning_contentby openai-chat, so both contracts hold at once.While in there, the extractor also picked up the per-block flush from
src/images/loop.ts. The old single-signature accumulator collapsedthinking(a), sig1, thinking(b), sig2into"ab"undersig2— the same replay-400 bug that file already guards against — and aggregated redacted blocks ahead of all visible thinking instead of keeping stream order.Three regression tests in
tests/web-search.test.ts: the replay shape for a raw-only stream (asserting the part carries no signature), exact part order and per-block signatures for a mixed redacted/signed/raw stream, and a realcreateOpenAIChatAdaptertest that reads the second routed request body to provereasoning_contentactually lands besidetool_calls.fix(web-search): carry raw reasoning into the replayed web_search turn
chore(devlog): advance the plan submodule to f76fc29