openai / openai/codex

[Windows] Thread heartbeat automation injects a function_call_output without call_id and permanently breaks the thread on strict Responses API providers

Open
#44,519 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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

Description

@etraut-openai — filing this because it a) bricks a thread permanently and b) is a protocol-level issue (Codex emits an item that fails strict Responses API schema validation).

What version of Codex App are you using (From "About Codex" dialog)?

26.903.71938 (Windows x64). Bundled Codex core: codex-cli 0.153.4.

What subscription do you have?

None — this install uses a third-party model provider via the Responses wire API (DeepSeek, base_url = "https://api.deepseek.com", wire_api = "responses").

What platform is your computer?

Windows 11 x64 (Microsoft Windows NT 10.0.26200.0).

What issue are you seeing?

A scheduled thread heartbeat automation wrote an invalid item into the thread history. After the first heartbeat fired, every subsequent request in that thread failed with a 400 and the thread became permanently unusable:

{"error":{"message":"Failed to deserialize the JSON body into the target type: input: missing field `call_id` at line 1 column 3173902","type":"invalid_request_error","param":null,"code":"invalid_request_error"}}

The malformed item in the rollout is a response_item of type function_call_output that has no call_id, even though its name is automation_update and its output is the heartbeat payload:

{"type":"function_call_output","id":"fco_01a08b47-193c-7ca3-8d64-f97435ad21e9","name":"automation_update","namespace":"codex_app","output":"<heartbeat>\r\n  <automation_id>10</automation_id>\r\n  ...\r\n</heartbeat>\r\n","internal_chat_message_metadata_passthrough":{"turn_id":"01a08b47-1934-7b72-8f31-93217d57f8bd","create_time":1789043087.6964047}}

The heartbeat spec says the heartbeat should arrive as a user message wrapped in a <heartbeat> XML tag, not as a tool result.

What steps can reproduce the bug?
  1. In a thread, create a heartbeat automation targeting that thread (kind="heartbeat", destination="thread", targetThreadId=<thread>, short interval, e.g. FREQ=MINUTELY;INTERVAL=1).
  2. Wait for the first heartbeat injection.
  3. Send any message in that thread, using a third-party provider over the Responses wire API that validates the request schema strictly (here: DeepSeek).
What is the expected behavior?

The heartbeat is delivered as a user message (per the heartbeat spec), and the thread keeps working.

What actually happens
  • The heartbeat is persisted as a function_call_output for the automation_update tool (namespace codex_app) without call_id, plus a matching event_msg item_completed with type: FunctionCallOutput.
  • Because the malformed item stays in history, every later turn replays it and is rejected with the same error (the reported column grows with history size: 3173902 then 3174148).
  • The thread can never recover on its own: requests never reach the model, so no auto-compaction can prune the item. Only a new thread works.
Timeline (UTC, 2026-09-10)
time event
12:14:35 automation created (function_call automation_update, call_id=call_00_LAK9DbjniBIKDdDlwLTX1933)
12:24:47 first heartbeat injected -> malformed function_call_output appended to the thread rollout (rollout-...01a014f9-....jsonl, line 1936)
12:24:50 turn fails: missing field call_id ... column 3173902
12:27:24 same failure, column 3174148
12:44:26 same failure (thread still broken)
Impact
  • The affected thread is bricked: no message can be sent until the history is repaired manually.
  • Any user who attaches a heartbeat automation to a thread can hit this; on providers that don't validate strictly it may silently pass, so the failure mode is provider-dependent.
Workaround
  1. Locate the thread rollout JSONL and find the function_call_output item that lacks call_id.
  2. Replace it with a regular message item (role user) that carries the heartbeat text — keeping the byte length identical (or, after editing, delete the thread's rows from thread_history_1.sqlite so it re-materializes).
  3. Restart the Codex app. In our case the thread resumed normally afterwards (verified with successful turns a minute later).
Suggested fix
  • Deliver heartbeats as ordinary user messages (as the heartbeat spec advertises), or
  • if a tool-result item must be written, include the originating call_id, or
  • don't emit any function_call_output for non-tool events.

Related: #44378 (a different history item that strict third-party Responses providers also reject).

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 thread heartbeat automation from its injection path to the rollout JSONL, focusing on the response_item that becomes a function_call_output. Inspect the related thread_history_1.sqlite materialization behavior and the matching event_msg item_completed record. Done means heartbeats are persisted as user messages, or tool results include call_id, and subsequent strict Responses API requests succeed.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.