mirror of
https://github.com/deepseek-ai/awesome-deepseek-integration.git
synced 2026-11-05 00:57:30 +00:00
[GH-ISSUE #740] Responses API: No tool output found for tool call <id> when any item sits between function_call and its function_call_output #207
Labels
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
deepseek-ai/awesome-deepseek-integration#207
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @LiTaoHH on GitHub (Sep 20, 2026).
Original GitHub issue: https://github.com/deepseek-ai/awesome-deepseek-integration/issues/740
Summary
POST /v1/responsesrejects a request whoseinputarray contains afunction_calland its matchingfunction_call_outputthat are not strictly adjacent. Any item in between — an extramessage(roledeveloper/user) or areasoningitem — makes the pairing check fail with:even though the matching
function_call_outputis present later in the sameinputarray. The OpenAI Responses API pairs tool calls with their outputs bycall_idregardless of position, so clients written against that behaviour break here — Codex in particular.Evidence
Four minimal non-streaming requests to
https://api.deepseek.com/v1/responses,model: deepseek-flash,max_output_tokens: 16. They differ only in the position of one extra item.A — extra
message(roledeveloper) between call and output → 400A2 — extra
reasoningitem between call and output → 400, same errorSame array as A, with the 4th item replaced by:
B — control:
function_callimmediately followed by its output → 200Same array as A with the interleaved item removed. Returns a normal response object.
C — control: the extra item placed after the output → 200
The failure is therefore purely positional: the tool output is present and correctly addressed by
call_id, it is only not adjacent.Note: a request that omits the
reasoningitem entirely is rejected withThe reasoning_text in the thinking mode must be passed back to the API.— that appears to be intentional (thinking mode), and the requests above all include the reasoning item, so it is unrelated to this bug.Impact on the official Codex integration
This is not a corner case for Codex (https://api-docs.deepseek.com/quick_start/agent_integrations/codex):
hookSpecificOutput.additionalContextreturned byPostToolUsehooks, agent-harness advisories, and reasoning items land between a tool call and its result.storeis not supported), Codex replays the whole history on the next request, including the orphanedfunction_call. Every following request in that conversation then fails with the samecall_id, so the thread is permanently wedged: the only recovery is starting a new conversation.In our session logs one thread failed three times in a row with an identical
call_id(call_01_7nfQ0nYFNRkRLiZZS1xE3774), and the same failure appeared on three separate days, always with an injected developer message sitting between the call and its output.Expected
function_call/custom_tool_callshould be paired with theirfunction_call_output/custom_tool_call_outputbycall_idanywhere ininput, the way the OpenAI Responses API does, instead of requiring positional adjacency.Suggested fix
When merging input items into the upstream chat messages, do not require the output to be the immediately following item:
tool_callsmessage together with the matching tool messages only once the outputs have been seen; orThe compatibility page hints at the current assumption:
function_call— "Merged into the adjacent assistant message",custom_tool_call— "with call_id pairing validation".Environment
deepseek-flashPOST /v1/responses, non-streaming,store: falseRelated
@oymy commented on GitHub (Sep 22, 2026):
One additional case: a normal multi-tool response triggers the same positional failure even without injected developer/user context.
Observed with deepseek-harness 0.1.6-alpha.2 using the pi-ai OpenAI Responses adapter. Both tools completed successfully and both outputs had matching
call_ids; the continuation failed only when replaying the history.This matters because Responses clients commonly preserve all calls from one assistant message before appending their results. Until the server pairs by
call_idregardless of position, a safe Harness workaround is to split only completed multi-call history and interleave each matched output on the outgoing request, without mutating durable session history.@cmzt-coder commented on GitHub (Oct 9, 2026):
Additional data point: the failure is deterministic, and forking a wedged thread does not recover it
Thanks for the four-request positional proof — it matches exactly what I see from the client side. I'm adding session-log evidence from a different Codex build, because two of the operational statements above turned out differently in my sample.
Environment (delta vs. the report above)
26.1002.52244(reportedcli_version: 0.162.0-alpha.2)deepseek-flash,POST /v1/responses,store: false1. The mid-turn item is not limited to
PostToolUsehooks —PreToolUsedoes it tooThe report above attributes the injected item to
PostToolUsehooks. In my sample all four injections came from aPreToolUsehook withmatcher: Bash, whose allow path returnshookSpecificOutput.additionalContext. It is persisted as adevelopermessage and lands between the assistant'sfunction_calland itsfunction_call_output.So whenever a
PreToolUsehook accepts a tool call and attaches context, the very next request in that turn is rejected. The repro matrix above is really about "any non-tool item in that position", andPreToolUseoutput belongs in it as well.2. The hit rate is 100%, not intermittent
Across the 149 rollouts there are exactly 4 hook-context injections in total, and:
function_calland its matchingfunction_call_output;Small sample, but the correlation is perfect: whenever the ordering occurs, the failure is immediate. Nothing else in those logs correlates with the error.
3. The damage outlives the failed turn
The illegal ordering is persisted, and because the API is stateless Codex replays it on every later request. Each retry fails again with the same
call_idand a newrequest_id:call_id;call_id;call_ids (one per injection).4. Forking a wedged thread is not a recovery path
The report says the only recovery is starting a new conversation. In my sample, forking the wedged thread does not help: the fork copies the illegal ordering, so its very first turn failed with the parent thread's
call_id. That is how a single injection propagated into a chain of three derived threads.5. Support for "purely positional": the output is always present
In all four cases the matching
function_call_outputis in the history, immediately preceded by the injected message. Nothing is missing; only the adjacency is broken. This independently supports the conclusion that the check is positional rather thancall_id-based.6. Adjacency is the discriminator, not tool-call pairing in general
Batched parallel tool calls are everywhere in these logs — sequences of 2–4
function_callitems followed by theirfunction_call_outputs — and none of them ever failed. That rules out a general "tool calls are paired incorrectly" explanation and leaves the in-between item as the trigger.Minimal trace (redacted)
Order and item kinds only; all ids, paths and command contents removed.
(Listed by order, not by log ordinal — the number of unrelated bookkeeping records between these items varies.)
Everything outside those four items is ordinary turn traffic (reasoning, assistant messages, token counts), and the requests in between differ only in
request_id.Ask
Unchanged from the report: pair
function_call/custom_tool_callwith their outputs bycall_idanywhere ininput, instead of requiring positional adjacency.One note for triage: a client-side change on the Codex side (buffering pending tool calls and flushing each call together with its output) would also remove this class of failure, and would protect users of any endpoint that is stricter than the OpenAI Responses spec. That is a separate upstream conversation — flagging it only so the root cause is not closed as client-only.