Codex Desktop: cross-thread prompts recorded as `function_call_output` without `call_id` cause request rejection
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
What version of the Codex App are you using (From “About Codex” dialog)?
OpenAI.Codex, version 26.908.4834.0`
What subscription do you have?
200$/mon
What platform is your computer?
Windows x64
What issue are you seeing?
Summary
In Codex Desktop on Windows, two app actions failed on the receiving thread:
- Sending an authorized handoff from a side conversation to an existing task with
send_message_to_thread. - Creating a clean replacement task with
create_thread, providing the handoff as its initial prompt.
In both cases, the receiving rollout contains a synthetic function_call_output item carrying the prompt but no call_id field. The next model request fails with:
Failed to deserialize the JSON body into the target type:
input: missing field `call_id`
Both receiving tasks used deepseek-flash. The new task failed on its first request, before producing any assistant response. This suggests a compatibility issue in the app-generated prompt injection / request serialization path, rather than a failure in the user's project or task instructions.
The exact component responsible for translating these synthetic records into the outgoing request has not been established. This report does not include a raw HTTP request capture or a code-level root-cause analysis.
Environment
| Item | Observed value |
|---|---|
| Application | Codex Desktop, Windows x64 |
| Installed Windows package | OpenAI.Codex, version 26.908.4834.0 |
| OS reported by runtime | Microsoft Windows 10.0.26200 |
Rollout cli_version |
0.154.0-alpha.6.2 |
Rollout originator |
Codex Desktop |
Rollout source |
vscode |
| Receiving model, both cases | deepseek-flash, verified from turn_context |
| Receiving provider identifier | openai_http, verified from session_meta |
| Sending side conversation | GPT-6-family session according to its active session context; exact serialized source model identifier was not independently captured |
| Date | 2026-09-13 |
| Time zone | Asia/Shanghai, UTC+08:00 |
Provider credentials and endpoint details are intentionally omitted. The provider identifier alone does not identify where request normalization occurs or which service rejected the request.
Incident A: sending a message to an existing task
Observed steps
- An existing project task was running with
deepseek-flashand had successfully completed many tool calls. - In a side conversation, the user requested an explicit scope adjustment and asked the assistant to send the updated instructions to the main task.
- The assistant called the Codex app tool
send_message_to_threadwith the destination thread ID and a plain-text handoff prompt. - The tool returned the destination thread ID with
isError: false. - The destination rollout recorded the handoff as the following item.
- The destination's next model request failed; the thread status became
systemError, and the turn status becamefailed.
Sanitized rollout evidence
Identifiers and handoff content are redacted. Field names and the absence of call_id are preserved.
{
"timestamp": "2026-09-13T14:58:11.309Z",
"type": "response_item",
"payload": {
"type": "function_call_output",
"id": "fco_<redacted>",
"name": "send_message_to_thread",
"namespace": "codex_app",
"output": "<codex_delegation>\n <source_thread_id><redacted></source_thread_id>\n <input><authorized handoff prompt redacted></input>\n</codex_delegation>"
}
}
The actual injected output was 5,638 characters. Immediately preceding normal tool-result records contained a call_id matching their tool calls. This synthetic record did not.
Exact receiving error
{
"error": {
"message": "Failed to deserialize the JSON body into the target type: input: missing field `call_id` at line 1 column 4412305",
"type": "invalid_request_error",
"param": null,
"code": "invalid_request_error"
}
}
The large column number is the reported parsing position. It is not evidence that the prompt exceeded a size or context limit.
Incident B: creating a clean replacement task
Observed steps
-
After identifying the first error, the user explicitly asked to continue in a new task and carry over the full handoff prompt, without repairing the old conversation.
-
The assistant used
create_threadwith:- A project target using its existing local checkout.
- A fresh title and a plain-text initial prompt.
- No explicit model override; the app-selected model was
deepseek-flash.
-
The tool returned a new
threadIdandhostId, withisError: false. -
This was a newly created task, not a fork of the failing task.
-
A read-only status check showed the new task had already failed on its first model request, with no assistant response.
Sanitized rollout evidence
{
"timestamp": "2026-09-13T15:10:43.522Z",
"type": "response_item",
"payload": {
"type": "function_call_output",
"id": "fco_<redacted>",
"name": "create_thread",
"output": "<initial handoff prompt redacted>"
}
}
This is an excerpt of the relevant fields. The actual record had no call_id field. Its output was 9,787 characters.
Exact receiving error
{
"error": {
"message": "Failed to deserialize the JSON body into the target type: input: missing field `call_id` at line 1 column 115358",
"type": "invalid_request_error",
"param": null,
"code": "invalid_request_error"
}
}
This second incident shows that replaying a long or already-failing conversation is not necessary to encounter the error. It also means that merely waiting until the target is idle cannot be assumed to solve it: a newly created task failed before it could do any work.
Expected behavior
- An app-supplied handoff or initial prompt should arrive in a representation accepted by the receiving model/provider.
- If it is ordinary user input, it should be serialized as an appropriate input message, or normalized from the app's internal representation before transmission.
- If it is a genuine tool result, it should retain the valid association with its corresponding tool call.
- If a particular model/provider does not support the operation, the app should give an actionable compatibility error before persisting unusable input into the destination's active context.
- Dispatch success and successful destination startup should be distinguishable. Returning a thread ID should not leave the user unaware that the receiving task immediately failed.
Actual impact
- An ongoing task was interrupted during a functional acceptance workflow.
- The requested scope adjustment was recorded, but there was no successful subsequent model response showing it had been processed.
- A new replacement task also failed immediately.
- The user had to handle continuation manually after a file-based handoff was prepared.
This report does not claim repository data loss, automatic rollback, or a project application failure. The failure observed here is at the conversation/model-request boundary.
What is confirmed, and what remains a hypothesis
Confirmed
- Both receiving tasks recorded
deepseek-flashas their model. - Both failing paths introduced a synthetic
function_call_outputcarrying task instructions and missingcall_id. - Both receiving requests failed with an explicit missing-
call_iddeserialization error. - An ordinary tool-result item immediately before the first injection contained a
call_id. - The second task was newly created and failed on its first request.
Not established
- Whether a same-model sender/receiver combination would also fail. No controlled comparison was performed.
- Whether the same operation succeeds with other receiving providers or models.
- Whether the defect belongs to the Desktop injection layer, its app-server serialization, a provider adapter, or another normalization layer.
- Whether the exact stored JSON item was transmitted unchanged; the raw outgoing request was not captured.
- Whether simply retrying or switching models can safely recover an already-affected task.
Different sender and receiver models may expose a compatibility gap, but the evidence does not establish that using different models is inherently invalid or is the root cause.
Suggested investigation and regression coverage
- Trace how
codex_app.send_message_to_threadandcodex_app.create_threadrepresent external task input in the destination, and how that representation is normalized for each supported provider. - Check whether these synthetic items are being passed through as tool results even when they are actually user instructions.
- Validate required fields and call/result associations before sending the request. Do not repair the issue by inserting an arbitrary
call_idwithout a corresponding call. - Cover both a running destination and a fresh task with a short, harmless prompt; compare same-model and different-model combinations, including a
deepseek-flashreceiver where supported. - Verify that a returned task ID is followed by clear asynchronous startup/failure visibility in the originating conversation or task UI.
- Provide a supported recovery path for a task containing such an item, so users do not need to edit rollout JSON or abandon their task history.
These are proposed checks, not tests that were run during this incident.
Temporary operational workaround
In the affected environment, avoid relying on the two automated cross-thread actions until compatibility has been verified. Save the handoff as a local Markdown file, then use an ordinary user message in a manually created clean task to ask it to read that file and continue.
The user reported handling continuation manually. This report does not assert that every possible manual recovery path or provider combination has been validated.
Reference
OpenAI function calling guide: tool output should reference the specific model tool call, using call_id in the Responses examples.
Only sanitized excerpts are included here. Full rollout files may contain private prompts, workspace information, tool output, or credentials and should not be attached to a public issue without a separate redaction review.
What steps can reproduce the bug?
This has already been described in detail in the text above.
What is the expected behavior?
No response
Additional information
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
The report names no source files; start by tracing codex_app.send_message_to_thread and codex_app.create_thread through destination storage and provider request normalization. Compare running and fresh tasks with short prompts, then verify that synthetic inputs have valid representations, failures are visible to users, and affected tasks have a supported recovery path.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- api, backend-api-design, devtools
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100