Skip to content

function_call_output for send_message_to_thread is persisted without call_id, breaking strict Responses API providers #45914

Open

Description

@Fuyulnk

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
    /responses endpoint

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 no name).
  • 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_HOME if that would help.

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    custom-modelIssues related to custom model providers (including local models)
    subagentIssues involving subagents or multi-agent features
    sessionIssues involving session (thread) management, resuming, forking, naming, archiving
    on Sep 16, 2026
  2. pluto1747-cybr commented on Sep 25, 2026

    @pluto1747-cybr

    Fresh cross-platform reproduction on Codex Desktop 26.917.71314 (still present)

    Environment

    Repro — three routes, all fail on the first turn

    1. create_thread with a one-line prompt → 422
    2. send_message_to_thread to that thread → 422
    3. fork_thread, then send_message_to_thread → 422

    Request ids

    • macOS create_thread: 878daf04-78fb-4e44-8642-d706df6ba2f7
    • Windows create_thread: 467f7001-4874-4505-a971-7755485a6715
    • Windows send_message_to_thread: e63e51b5-4ffa-4c62-89cf-a3fab8553a61
    • Windows fork_thread + send: 57b05ca4-6953-44dd-8938-016a6fab58de

    Rollout evidence (macOS, rollout-2026-09-25T19-13-52-01a0d845-9150-7801-9807-e9b9799e130d.jsonl)

    • line 6: turn_context with multi_agent_version=v1
    • line 8: FunctionCallOutput item keys = id, name, namespace, output, type — no call_id
    • line 9: unexpected status 422 Unprocessable Entity: ... input: missing field 'call_id' ... url: https://api.deepseek.com/responses

    Working 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 the keep_forked_rollout_item analysis 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.

  3. pluto1747-cybr commented on Oct 14, 2026

    @pluto1747-cybr
  4. kolya182 commented on Oct 14, 2026

    @kolya182

    @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 main source still creates the handoff result as a function_call_output without a call_id, and the custom-provider request path does not convert it. Please triage this report and confirm the owner or fix plan.

  5. MilesBao commented on Nov 7, 2026

    @MilesBao

    Additional evidence from Linux on 2026-11-07: create_thread delegation rejected by a custom Responses provider

    Environment:

    • Linux remote host; Codex Desktop connected to the remote App Server.
    • CLI/App Server version recorded in the affected rollouts: 0.156.1.
    • Model: gpt-6.1-sol through a custom Packy Responses provider and a local forwarding router.
    • Desktop client version was not collected.

    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_id on this item and no corresponding function_call in the initial rollout. The session metadata marks it as agent_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:

    No tool call found for function call output with call_id fco_REDACTED.
    type: packy_invalid_request_error
    param: input
    code: invalid_request_error
    

    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_id would not establish a matching call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    appIssues related to the Codex desktop appbugSomething isn't workingcustom-modelIssues related to custom model providers (including local models)sessionIssues involving session (thread) management, resuming, forking, naming, archivingsubagentIssues involving subagents or multi-agent features

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions