Skip to content

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 #43515

Open

Description

@owenfuzhe

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

No API keys or credentials are included in this report.

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 7, 2026
  2. github-actions commented on Sep 7, 2026

    @github-actions
    Contributor

    Potential duplicates detected. Please review them and close your issue if it is a duplicate.

    Powered by Codex Action

  3. RuriLothlorien commented on Sep 10, 2026

    @RuriLothlorien

    Adding a Windows + DeepSeek data point for the same root cause (@owenfuzhe) — a single thread heartbeat automation was enough to poison a thread permanently.

    So: heartbeat automations are affected as well, and the failure is provider-independent in shape (missing field call_id / tool_call_id is not found / invalid_prompt depending on the endpoint's validator).

  4. TimMCBen commented on Sep 18, 2026

    @TimMCBen

    Adding a Windows data point on a newer build, plus one detail not yet reported here: every heartbeat run re-injects another call_id-less record before the request goes out, so the thread accumulates one malformed item per scheduled fire — 18 in one thread and 40 in another over a single day.

    Environment

    • Codex Desktop (Windows 11 x64): app version 26.908.70816
    • Provider: third-party relay exposing Kimi models (base_url = "https://api2.mcben.top/v1", wire_api = "responses"), models kimi-k2.8 / kimi-k3
    • Normal interactive turns through the same provider work fine.

    What happened

    Three heartbeat automations (interval 30 min, attached to existing threads). Every single run followed the same sequence in the rollout JSONL (~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl):

    1. task_started
    2. a response_item of type function_call_output with name: "automation_update", namespace: "codex_app", an fco_... id, the <heartbeat> instructions payload — and no call_id
    3. task_complete with error after ~1.4 s, model never engaged:
    {"error":{"type":"invalid_request_error","message":"tool_call_id  is not found"}}

    (note the double space — the relay converts the absent call_id into an empty tool_call_id, consistent with the proxy-boundary observation by @ZzkAItech above).

    The accumulation detail

    Because the malformed injection is persisted into the thread history before the request is assembled, each failed run leaves its <heartbeat> output in the rollout, and the next fire adds one more. Counts from the affected threads' rollouts:

    • Thread A (heartbeat created 2026-09-17): 146 function_call_output records, 18 without call_id — all automation_update heartbeat injections, spaced exactly at the 30-min schedule.
    • Thread B (heartbeat created 2026-09-16): 359 records, 40 without call_id — same pattern.
    • Thread C (created 2026-09-18 by forking): inherited 1 malformed record from its parent at fork time, i.e. keep_forked_rollout_item carried the poison into the child thread, matching the spawn.rs behaviour described by @argszero.

    So on this provider the failure is deterministic from the very first heartbeat fire — even a perfectly clean thread gets poisoned by its own trigger injection before the request is sent, and no amount of rollout repair can keep a heartbeat thread healthy, since the next run re-poisons it.

    Notes

    • The exact same shape (function_call_output with id/name/namespace/output and no call_id) is what gets written on every fire; timestamps line up 1:1 with the schedule.
    • Affected thread ids (2026-09-17/18, Asia/Shanghai): 01a0a46a-b29a-78d0-aa10-037aacaa7390 (heartbeat ali3-imder-iemocap), 01a0a5e7-b041-70a3-9521-877d9edee4bb (heartbeat videollama2-meld), 01a0b20c-fa82-7672-8112-11259062a1e5 (heartbeat videollama2-meld-2, forked).
    • This matches the turn/start toolOutput producer path pinned down by @argszero (turn_processor.rs hardcoding call_id: None). For strict providers, treatment option 1 from that analysis (rewrite to a plain message at request assembly, gated on a provider capability flag) is also what our manual rollout repair converged to: converting the record to a plain user message un-bricks the thread.

    No credentials, prompt contents, or server addresses included.

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 appautomationsbugSomething 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