openai / openai/codex

Codex Desktop: cross-thread prompts recorded as `function_call_output` without `call_id` cause request rejection

Open
#45,318 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

app app-server bug custom-model subagent windows-os
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:

  1. Sending an authorized handoff from a side conversation to an existing task with send_message_to_thread.
  2. 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
  1. An existing project task was running with deepseek-flash and had successfully completed many tool calls.
  2. In a side conversation, the user requested an explicit scope adjustment and asked the assistant to send the updated instructions to the main task.
  3. The assistant called the Codex app tool send_message_to_thread with the destination thread ID and a plain-text handoff prompt.
  4. The tool returned the destination thread ID with isError: false.
  5. The destination rollout recorded the handoff as the following item.
  6. The destination's next model request failed; the thread status became systemError, and the turn status became failed.
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
  1. 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.

  2. The assistant used create_thread with:

    • 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.
  3. The tool returned a new threadId and hostId, with isError: false.

  4. This was a newly created task, not a fork of the failing task.

  5. 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-flash as their model.
  • Both failing paths introduced a synthetic function_call_output carrying task instructions and missing call_id.
  • Both receiving requests failed with an explicit missing-call_id deserialization 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

  1. Trace how codex_app.send_message_to_thread and codex_app.create_thread represent external task input in the destination, and how that representation is normalized for each supported provider.
  2. Check whether these synthetic items are being passed through as tool results even when they are actually user instructions.
  3. Validate required fields and call/result associations before sending the request. Do not repair the issue by inserting an arbitrary call_id without a corresponding call.
  4. Cover both a running destination and a fresh task with a short, harmless prompt; compare same-model and different-model combinations, including a deepseek-flash receiver where supported.
  5. Verify that a returned task ID is followed by clear asynchronous startup/failure visibility in the originating conversation or task UI.
  6. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.