Client or integration
Codex App
Area
Tools / MCP / web search
Summary
The hosted Web Search loop can emit a successful response.completed with no assistant message after the search budget is exhausted and the routed model's forced-answer pass returns a malformed tool call.
In the observed DeepSeek V4 Flash turn, a batched web_search consumed the three-search budget. OpenCodex then made the forced-answer request without the synthetic search tool. The upstream response was HTTP 200 with stop_reason=tool_calls, content=null, an empty function name, an empty call ID, and arguments shaped like another batched search. OpenCodex treated that response as terminal success. Codex App showed the completed search cell but no final answer.
This is distinct from #875/#946: the client turn does not remain pending and a continuation request is not missing. The internal hosted-search iterations and forced-answer request all occur, after which OpenCodex sends task_complete / response.completed with no visible assistant message.
Expected: a malformed forced-answer tool call must not become a successful empty turn. OpenCodex should either recover with a bounded final-answer retry or surface an explicit in-stream error.
Reproduction
Provider-level reproduction (the model's malformed output is not guaranteed on every prompt):
- Run OpenCodex 2.10.0 with a routed
openai-chat provider backed by an OpenAI-compatible Bifrost endpoint and select deepseek-v4-flash.
- Use Codex App and request a broad research task that causes the synthetic hosted
web_search function to batch at least three queries. For example: ask for a current comparison of OpenClaw and Hermes Agent based on community reports.
- Let the batch consume the configured/default
maxSearches=3 budget.
- Observe the forced-answer request. In the failing case, the upstream response is HTTP 200 and ends with
stop_reason=tool_calls, but contains no text and has an empty tool-call name/ID.
- Codex App displays the completed search cell and then stops with no assistant answer. The rollout records
task_complete, not a pending/stalled or failed turn.
Deterministic local regression probe on current dev (e44d234f08e03dd4dbf0c4aa13af43046d86b0a6):
- Use the existing
runWithWebSearch test harness and maxSearches: 2.
- Make adapter pass 1 emit:
tool_call_start id="call_search" name="web_search"
tool_call_delta {"queries":["one","two"]}
tool_call_end
done
- Make the forced-answer pass emit:
tool_call_start id="" name=""
tool_call_delta {"queries":["three"]}
tool_call_end
done
- Drain the response SSE. Current
dev emits response.completed with status="completed" and no output item of type message. The focused probe passes when asserting this current, incorrect behaviour:
[web-search-loop] done — 2 searches, 2 iterations
[web-search-loop] cancelled — 2 real searches, 0 placeholders
response.completed status=completed
assistant message present=false
Relevant code path on current dev:
src/web-search/loop.ts:72-75: every non-web_search name, including an empty name, sets hasRealToolCall=true.
src/web-search/loop.ts:681-705: forceAnswer=true makes shouldLoop=false; split.passthrough is replayed and the loop returns without checking for visible text or a valid tool call.
- The forced-answer tests use helpers whose later pass always returns text (
"final answer"), so the malformed/empty terminal case is not covered.
Version
@bitkyc08/opencodex 2.10.0 (f9b9440c551e3d7f3e2041098caa2ee4de57698e). The deterministic regression probe also reproduces on current dev at e44d234f08e03dd4dbf0c4aa13af43046d86b0a6 (2026-08-04).
Operating system
OpenCodex host: Ubuntu 24.04 x86_64. Client: Codex App on Windows.
Provider and model
OpenAI-compatible Bifrost 1.6.5 route to Alibaba Token Plan International / deepseek-v4-flash; OpenCodex adapter: openai-chat.
Logs or error output
# Failing live turn, redacted and condensed
initial routed request: tools=17
model output: stop_reason=tool_calls, batched web_search queries
hosted searches executed: 3
forced-answer routed request: tools=16 (synthetic web_search removed)
upstream HTTP status: 200
stop_reason: tool_calls
content: null
tool call id: ""
function name: ""
arguments: {"queries":["..."]}
[web-search-loop] done — 3 searches, 2 iterations
[web-search-loop] cancelled — 3 real searches, 0 placeholders
Codex lifecycle:
web_search_end
task_complete
last_agent_message=null
The Bifrost request log shows that both failing forced-answer requests returned HTTP 200 within seconds. One earlier attempt encountered a transient 429, but the final request succeeded with HTTP 200, so the 429 is not the terminal cause.
Screenshots and supporting files
A deterministic focused test was run against current dev: 1 test passed, confirming that the loop currently returns a completed response without a message for the event sequence above. A minimal regression patch can be provided if useful.
Related but distinct reports:
Redacted configuration
{
"providers": {
"redacted-provider": {
"adapter": "openai-chat",
"baseUrl": "https://redacted.example/v1",
"authMode": "key",
"defaultModel": "deepseek-v4-flash",
"models": ["deepseek-v4-flash"],
"preserveReasoningContentModels": ["deepseek-v4-flash"]
}
}
}
Checks
Client or integration
Codex App
Area
Tools / MCP / web search
Summary
The hosted Web Search loop can emit a successful
response.completedwith no assistant message after the search budget is exhausted and the routed model's forced-answer pass returns a malformed tool call.In the observed DeepSeek V4 Flash turn, a batched
web_searchconsumed the three-search budget. OpenCodex then made the forced-answer request without the synthetic search tool. The upstream response was HTTP 200 withstop_reason=tool_calls,content=null, an empty function name, an empty call ID, and arguments shaped like another batched search. OpenCodex treated that response as terminal success. Codex App showed the completed search cell but no final answer.This is distinct from #875/#946: the client turn does not remain pending and a continuation request is not missing. The internal hosted-search iterations and forced-answer request all occur, after which OpenCodex sends
task_complete/response.completedwith no visible assistant message.Expected: a malformed forced-answer tool call must not become a successful empty turn. OpenCodex should either recover with a bounded final-answer retry or surface an explicit in-stream error.
Reproduction
Provider-level reproduction (the model's malformed output is not guaranteed on every prompt):
openai-chatprovider backed by an OpenAI-compatible Bifrost endpoint and selectdeepseek-v4-flash.web_searchfunction to batch at least three queries. For example: ask for a current comparison of OpenClaw and Hermes Agent based on community reports.maxSearches=3budget.stop_reason=tool_calls, but contains no text and has an empty tool-call name/ID.task_complete, not a pending/stalled or failed turn.Deterministic local regression probe on current
dev(e44d234f08e03dd4dbf0c4aa13af43046d86b0a6):runWithWebSearchtest harness andmaxSearches: 2.devemitsresponse.completedwithstatus="completed"and no output item of typemessage. The focused probe passes when asserting this current, incorrect behaviour:Relevant code path on current
dev:src/web-search/loop.ts:72-75: every non-web_searchname, including an empty name, setshasRealToolCall=true.src/web-search/loop.ts:681-705:forceAnswer=truemakesshouldLoop=false;split.passthroughis replayed and the loop returns without checking for visible text or a valid tool call."final answer"), so the malformed/empty terminal case is not covered.Version
@bitkyc08/opencodex2.10.0 (f9b9440c551e3d7f3e2041098caa2ee4de57698e). The deterministic regression probe also reproduces on currentdevate44d234f08e03dd4dbf0c4aa13af43046d86b0a6(2026-08-04).Operating system
OpenCodex host: Ubuntu 24.04 x86_64. Client: Codex App on Windows.
Provider and model
OpenAI-compatible Bifrost 1.6.5 route to Alibaba Token Plan International /
deepseek-v4-flash; OpenCodex adapter:openai-chat.Logs or error output
The Bifrost request log shows that both failing forced-answer requests returned HTTP 200 within seconds. One earlier attempt encountered a transient 429, but the final request succeeded with HTTP 200, so the 429 is not the terminal cause.
Screenshots and supporting files
A deterministic focused test was run against current
dev: 1 test passed, confirming that the loop currently returns a completed response without a message for the event sequence above. A minimal regression patch can be provided if useful.Related but distinct reports:
reasoning_contentreplay failures.querieson replayed web_search_call #930 / fix(web-search): carry queries on a single-query search so strict parsers accept the replay #932: missingquerieson replayed single-queryweb_search_call.Redacted configuration
{ "providers": { "redacted-provider": { "adapter": "openai-chat", "baseUrl": "https://redacted.example/v1", "authMode": "key", "defaultModel": "deepseek-v4-flash", "models": ["deepseek-v4-flash"], "preserveReasoningContentModels": ["deepseek-v4-flash"] } } }Checks