Repository navigation
Description
Description
POST https://opencode.ai/zen/go/v1/responses (model deepseek-v4.1-flash) rejects a body in which a non-tool item sits between the two function_call_output items of one assistant step, and names a tool call that does have an output:
{"error":{"message":"No tool output found for tool call call_01_b.","type":"invalid_request_error","param":null,"code":"invalid_request_error"}}Every call_id in that body has a matching output, so the error text is misleading. The outcome also depends on the interleaved item's text: <image_resize_notice>...</image_resize_notice> is accepted, any other text is rejected - so the gateway appears to string-match that one literal token and treat anything else as a user message that ends the tool-result group.
This matters for real clients: in the Responses API input is a flat item sequence and results are associated by call_id, so a client may legitimately place a non-tool item between two tool outputs. Codex does exactly that - when an inline image is resized it appends an <image_resize_notice> developer message between the image tool output and the other tool output of the same batch.
Steps to reproduce
Two requests to https://opencode.ai/zen/go/v1/responses, identical except for one string. Request 1 returns HTTP 400, request 2 returns HTTP 200.
API=https://opencode.ai/zen/go/v1/responses
KEY=$ZEN_API_KEY # any Zen/Go key with deepseek-v4.1-flash
# 400: interleaved text is anything other than the literal image_resize_notice token
curl -s -X POST "$API" \
-H "content-type: application/json" -H "authorization: Bearer $KEY" \
-H "user-agent: curl/8.4.0" -H "x-opencode-session: repro-1" -d '{
"model":"deepseek-v4.1-flash","stream":false,"max_output_tokens":16,
"input":[
{"type":"message","role":"user","content":[{"type":"input_text","text":"say ok"}]},
{"type":"function_call","id":"fc_a","call_id":"call_00_a","name":"exec_command","arguments":"{}"},
{"type":"function_call","id":"fc_b","call_id":"call_01_b","name":"exec_command","arguments":"{}"},
{"type":"function_call_output","call_id":"call_00_a","output":"ok"},
{"type":"message","role":"developer","content":[{"type":"input_text","text":"x"}]},
{"type":"function_call_output","call_id":"call_01_b","output":"ok"}
]}'
# -> 400 "No tool output found for tool call call_01_b."
# 200: only the interleaved text changed
# "text":"<image_resize_notice></image_resize_notice>"Observed with the interleaved item's text / role (everything else identical):
| interleaved item | result |
|---|---|
<image_resize_notice></image_resize_notice>, role=developer |
200 |
<image_resize_notice>resized</image_resize_notice>, role=developer |
200 |
<image_resize_notice>...resized from 1024x9000 to 233x2048 pixels...</image_resize_notice>, role=developer |
200 |
| the same text with role=user / system / assistant | 400 |
x / a plain sentence |
400 |
<system_notice>x</system_notice> / <some_other_tag>x</some_other_tag> |
400 |
image_resize_notice (no tags) / IMAGE_RESIZE_NOTICE / image_resiz_notice (typo) |
400 |
Each case was repeated and is deterministic.
Impact
The 400 arrives before any generation, so the client's whole turn is aborted with no assistant output. In Codex the offending order is persisted in the session, so later turns in that thread fail the same way and the thread becomes unusable. Client-side reports of the same shape: openai/codex#52834 and openai/codex#44604.
Environment
- Endpoint/model:
https://opencode.ai/zen/go/v1/responses,deepseek-v4.1-flash(Go subscription) - Client: Codex desktop app 26.1007.21434 (codex-cli 0.162.0-alpha.17.2), Windows 11,
wire_api = "responses"; requests pass through a local OpenAI-compatible relay that forwards to Zen - Production occurrence: 2026-10-11 17:30:21 UTC; the error string on that request carried upstream request id
01271f1e-0095-4424-9a58-7ccb17b37182
Side observation
Which checks run also depends on how items are grouped: sending the same items with contiguous tool outputs triggers The 'reasoning_text' in the thinking mode must be passed back to the API, while the same items with an interleaved message do not. It looks like the item grouping decides which validation fires, which may be worth looking at together with the above.
Activity
opencode-agent commentedon Oct 12, 2026
Thanks for the detailed report and the two-request curl repro. I couldn't run it here: this behaviour is in the hosted Zen/Go gateway and testing it needs a Zen/Go API key, which this environment doesn't have. Without a key the endpoint rejects the request for the missing key before it looks at the
inputitems. A maintainer with Go access will need to check it. Your curl pair and the upstream request id should make that straightforward.Related reports about the Zen/Go
/v1/responsesendpoint with Codex-style clients:/v1/responsesreturns 422 when a tool output contains an image, which also breaks Codex sessionsdeepseek-v4-proon opencode-go/responsesfails on multi-turn requestsdeepseek-v4.1-flashsometimes returns HTTP 400Edward4226 commentedon Oct 12, 2026
Follow-up with (a) the tolerance measured directly against
POST https://opencode.ai/zen/go/v1/responses(deepseek-v4.1-flash,User-Agent: curl/8.9.1, streamed and not), and (b) a client-side fix.One assistant step, two parallel calls,
output_a, one interleaved developer message,output_b— the interleaved item's text is the only thing that changes:<image_resize_notice>…</image_resize_notice><image_resize_notice>x(unclosed),<some_other_tag>x</some_other_tag>,<Image_Resize_Notice>x</Image_Resize_Notice>No tool output found for tool call call_b.just a note, roleuser/system/assistantwith the notice textNo tool output found for tool call call_b.Stable over repeats. So the tool-result group appears to end at any interleaved item whose text isn't that one literal, and the check then reports a call whose output is in the list — the misleading part in the report.
Worth noting in the other direction: the report's own body (both outputs present, the notice between them) answers 200 at this endpoint, before and after our fix, so that particular 400 isn't reproduced by that body alone — it may be the client's follow-up body being assembled before the batch's last output was in it (openai/codex#44604). What is reproduced on this endpoint is the non-notice interleave above.
The client side (magpie, the local relay this came through) now normalizes the run before relaying: an item inside a call/result run goes after the run's results. Patch and evidence: yetone/magpie#1752
gateway: an item a batch's tool results were interrupted by goes afte…
gateway: an item a batch's tool results were interrupted by goes afte…