Repository navigation
Description
function_call_output for send_message_to_thread is persisted without call_id, breaking any strict Responses API provider
Summary
Rollout records for async cross-thread tool results (send_message_to_thread, namespace
codex_app) are written as function_call_output items without a call_id field.
OpenAI's own backend tolerates the malformed item. Any provider that strictly deserializes the
Responses API input array rejects the entire request:
Failed to deserialize the JSON body into the target type: input: missing field `call_id`
Because the bad record stays in thread history, the thread becomes permanently unusable on such
providers once that record falls inside the retained context window.
Environment
- Codex app (bundled codex):
0.154.0-alpha.6.2
(/Applications/ChatGPT.app/Contents/Resources/codex) - Codex CLI:
0.147.0 - macOS (Darwin 25.6.0), Apple silicon
- Provider: custom
[model_providers.*]entry pointing at an OpenAI-compatible
/responsesendpoint
Observed record
Taken from a rollout at ~/.codex/sessions/<yyyy>/<mm>/<dd>/*.jsonl:
{
"timestamp": "2026-09-16T...",
"type": "response_item",
"payload": {
"type": "function_call_output",
"id": "fco_01a06a8e-65c1-7e22-8b84-c42ca9cf1b6c",
"name": "send_message_to_thread",
"namespace": "codex_app",
"output": "<codex_delegation>...</codex_delegation>"
}
}call_id is absent. In the same thread, the corresponding calls are well-formed:
function_call send_message_to_thread 16 items — all carry call_id
function_call_output send_message_to_thread 6 items — none carry call_id
Positionally, these outputs appear at the start of a later turn (immediately following a
turn_context entry) — i.e. they are the async result of a delegation issued in a previous turn.
Scale and date range
Four long-lived threads in one workspace:
| thread | bad records | first occurrence |
|---|---|---|
| A | 284 | 2026-08-31 01:33:52 |
| B | 164 | 2026-08-31 08:57:50 |
| C | 221 | 2026-09-08 07:53:04 |
| D | 84 | 2026-09-04 03:55:12 |
- Tool attribution in thread A: 281 / 284 are
send_message_to_thread(3 have noname). - Month distribution in thread A (thread created 2026-07-18):
2026-07: 0,2026-08: 7,2026-09: 277.
Two independent threads both first show the defect on 2026-08-31, which should bracket the
regression window.
Minimal reproduction (API level)
Verified 2026-09-16 against a strict OpenAI-compatible /responses implementation:
POST {base_url}/responses
{
"model": "...",
"input": [
{"type": "message", "role": "user",
"content": [{"type": "input_text", "text": "hi"}]},
{"type": "function_call_output", "id": "fco_test", "output": "test"}
]
}
→ 400 {"error":{"message":"Failed to deserialize the JSON body into the target type:
input: missing field `call_id`", ...}}
Supplying an arbitrary call_id does not help either:
→ 400 {"error":{"message":"No tool call found for tool output with call_id call_test", ...}}
So the fix must produce an output that is paired with a real call, not merely populated.
Expected behaviour
Every function_call_output written to a rollout should carry the call_id of its originating
call. For async results whose originating call is no longer in the retained history, either
re-emit the call or drop the orphan output when constructing the Responses input array.
Impact
- Once a bad record enters the retained window, the thread fails on every turn and cannot be
recovered without manual mutation of the rollout files. - The async-result path keeps firing, so new bad records accumulate continuously — the
problem grows with use rather than being a one-off. - Users cannot tell which threads are affected until they hit the error; the failure looks like a
provider fault rather than a history-corruption fault.
Additional notes
- A codex-side log line exists:
Orphan function call output for call id: …, so orphan handling
is present — it just does not cover the missing field case, which is what breaks strict
deserializers. - Happy to provide redacted rollout excerpts or a script that counts affected records in a
CODEX_HOMEif that would help.
Activity
github-actions commentedon Sep 16, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
function_call_outputwithoutcall_idcause request rejection #45318function_call_outputwithoutcall_id, permanently breaking the target session (400 missing field call_id) #44723Powered by Codex Action
pluto1747-cybr commentedon Sep 25, 2026
Fresh cross-platform reproduction on Codex Desktop 26.917.71314 (still present)
Environment
26.917.71314(build 10954; Codex Framework 153.0.8010.53), bundled CLI0.155.0-alpha.16.4wire_api = "responses",base_url = https://api.deepseek.com, modeldeepseek-flashmulti_agent_version: v1(v2 is the separate known issue Subagent task payload invisible to custom non-OpenAI providers (DeepSeek): encrypted_content block dropped with multi_agent_version v2 #36586); threadturn_contextconfirmedmulti_agent_version: v1Repro — three routes, all fail on the first turn
create_threadwith a one-line prompt → 422send_message_to_threadto that thread → 422fork_thread, thensend_message_to_thread→ 422Request ids
create_thread:878daf04-78fb-4e44-8642-d706df6ba2f7create_thread:467f7001-4874-4505-a971-7755485a6715send_message_to_thread:e63e51b5-4ffa-4c62-89cf-a3fab8553a61fork_thread+ send:57b05ca4-6953-44dd-8938-016a6fab58deRollout evidence (macOS,
rollout-2026-09-25T19-13-52-01a0d845-9150-7801-9807-e9b9799e130d.jsonl)turn_contextwithmulti_agent_version=v1FunctionCallOutputitem keys =id, name, namespace, output, type— nocall_idunexpected status 422 Unprocessable Entity: ... input: missing field 'call_id' ... url: https://api.deepseek.com/responsesWorking control: a thread created manually in the UI can dispatch a subagent successfully on the same build/provider (v1 delivery + child repeats a verification token). The defect appears limited to threads the app creates programmatically (
create_thread/send_message_to_thread/fork_thread+ send), consistent with thekeep_forked_rollout_itemanalysis in #42088.New value here: still present on
26.917.71314, reproduces identically on macOS and Windows, and the manual-UI thread path is a working control.pluto1747-cybr commentedon Oct 14, 2026
kolya182 commentedon Oct 14, 2026
@etraut-openai @jif-oai Could you help route this issue to the team that owns the app-server and Responses request path?
I confirmed that this still reproduces with Codex CLI 0.162.1 and a custom Responses provider. Sending a read-only cross-thread message fails before the receiving thread runs:
Responses field 'input[].call_id' must be a non-empty string.The current
mainsource still creates the handoff result as afunction_call_outputwithout acall_id, and the custom-provider request path does not convert it. Please triage this report and confirm the owner or fix plan.MilesBao commentedon Nov 7, 2026
Additional evidence from Linux on 2026-11-07:
create_threaddelegation rejected by a custom Responses providerEnvironment:
0.156.1.gpt-6.1-solthrough a custom Packy Responses provider and a local forwarding router.The affected chat was created programmatically with
codex_app.create_thread. Its initial rollout contains this standalone item (identifiers and task text redacted):{ "type": "response_item", "payload": { "type": "function_call_output", "id": "fco_REDACTED", "name": "create_thread", "namespace": "codex_app", "output": "<codex_delegation>...redacted handoff...</codex_delegation>" } }There is no
call_idon this item and no correspondingfunction_callin the initial rollout. The session metadata marks it asagent_created_thread.Two turns failed with HTTP 400, at approximately 09:11 and 09:27 UTC on November 7. Both errors referenced the same
fco_...identifier from the initial delegation:The chat also executed tools successfully between those failures. Its later rollout contains 22 custom tool calls and 22 outputs, all paired by
call_id. Therefore this observation is intermittent; it does not demonstrate failure on every turn.The inspected local router changes the model name and forwards the JSON body, without normalizing delegation items. We did not capture the complete outbound payload of the failing request or compare with the first-party backend, so we cannot isolate the exact serialization or upstream validation step.
The user has moved the task to a manually created chat. We have not performed a controlled reproduction or a patched-client test. A proposed minimal reproduction is to create a chat with a harmless one-line prompt using
create_thread, then continue it through a strict Responses provider and inspect the persisted delegation item.This appears consistent with #45450 as well as this issue. Please ensure the provider-facing representation supports injected delegation context without requiring an unmatched tool result or dropping the handoff content. Merely inventing a
call_idwould not establish a matching call.