openai / openai/codex

Codex Desktop + Kimi provider: all injected inputs (heartbeat/cron automations, thread delegation) fail with "tool_call_id is not found" and permanently poison the thread

Open
#43,515 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

app automations bug custom-model session subagent
Dominant language
Rust
Stars
125k
Forks
19.4k
PR merge metrics
PR metrics pending

Description

Summary

On Codex Desktop for macOS with a custom Kimi provider (wire_api = "responses"), every turn whose input is delivered as a system-injected standalone function_call_output fails immediately with HTTP 400. This is the same root cause as #41690 (DeepSeek) and #41799 (Azure), but the blast radius is wider and the fallout is worse:

  1. All injection paths are affected, not just cron automations: heartbeat automations, cron automations, create_thread delegation (<codex_delegation>), and send_message_to_thread all deliver their input as a function_call_output with no call_id, and all fail identically.
  2. The orphaned item poisons the thread permanently: it is persisted into the rollout history, so every subsequent turn of that thread — including plain user-typed messages — fails with the same error. The thread is bricked until the rollout file is manually repaired.

Kimi's Responses endpoint rejects the request with:

{"error":{"code":"invalid_request_error","message":"Invalid request: tool_call_id  is not found","type":"invalid_request_error"}}

(note the empty tool_call_id — double space in the message).

Environment

  • Codex Desktop (macOS): 26.825.51511 (build 7377)
  • OS: macOS 14.6.1, Apple Silicon (arm64)
  • Provider config:
model_provider = "kimi"
model = "k3"

[model_providers.kimi]
name = "Kimi"
base_url = "https://api.kimi.com/coding/v1"
env_key = "[REDACTED]"
wire_api = "responses"

Normal interactive turns (user-typed messages, normal tool calls) through this same provider work fine.

Steps to reproduce

  1. Configure the Kimi provider as above.
  2. Do any of the following:
    • Create a heartbeat automation attached to a thread and wait for it to fire;
    • Create a cron automation and wait for it to fire;
    • From a working thread, call create_thread or send_message_to_thread.
  3. The new turn fails within ~5 seconds with tool_call_id is not found. The model never receives the prompt.

Evidence

Rollout files (~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl) show the injected item has keys [id, internal_chat_message_metadata_passthrough, name, namespace, output, type]no call_id:

{"type": "function_call_output", "id": "fco_01a07c84-bb4e-7ce2-b834-a3aa4dc6d3e9", "name": "automation_update", "namespace": "codex_app", "output": "<heartbeat>..."}
{"type": "function_call_output", "id": "fco_01a07c76-1120-73e3-8ed5-3b7acf8f35ce", "name": "create_thread", "namespace": "codex_app", "output": "<codex_delegation>..."}

In one affected thread, 150 function_call_output records were inspected: 149 normal tool outputs carry a call_id; exactly 1 — the heartbeat-injected automation_update — lacks it. That single record poisoned the whole thread: a heartbeat run at 09:31 failed, and two subsequent plain user messages in the same thread failed with the identical error.

Affected thread/session IDs (2026-09-07, Asia/Shanghai):

  • Heartbeat run: thread 01a0746b-0025-77b0-aad5-aeb71f25b5ad, turn started 09:31, failed in ~4.7s
  • Delegated thread via create_thread: 01a07c76-1005-7973-965f-6dfcab2bb6a1, failed in ~6.9s
  • Cron automation run: thread 01a07c84-b8a5-7d60-aa53-5e8f2bedc9b4, failed in ~9.5s
  • Delegated message via send_message_to_thread: same tool_call_id error

Local workaround (confirms root cause)

Manually editing the rollout JSONL to convert the orphaned function_call_output into a plain user message item un-bricks the thread. This isolates the failure to the injection/serialization layer — scheduler, credentials, network, and prompt content are all unrelated.

Expected behavior

Standalone system-injected function_call_output items should never be sent to strict Responses providers without a valid call_id. Either synthesize a paired function_call + call_id, or normalize the item into a user/developer message at the provider boundary (as proposed in #42376). Additionally, a failed injection should not leave a malformed item in the persisted history that bricks all future turns of the thread.

Related

  • #41690 — same root cause, DeepSeek, cron automations
  • #41799 — same root cause, Azure, cron automations on macOS
  • #42376 — same root cause with a proposed fix (normalize at the non-OpenAI provider boundary)
  • #42088 — dangling function_call_output breaking resumed threads

No API keys or credentials are included in this report.

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

Start by tracing system-injected function_call_output items through the provider boundary and into the persisted ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl history. Compare the proposed normalization in #42376 and reproduce with the heartbeat, cron, create_thread, or send_message_to_thread paths. Done means strict Responses providers no longer receive orphaned outputs and a failed injection does not permanently poison subsequent turns.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.