Skip to content

fix(codex): sanitize synthetic heartbeat tool outputs without call_id (#6995) - #7002

Open
loulanyue wants to merge 1 commit into
farion1231:mainfrom
loulanyue:fix/6995-codex-heartbeat-missing-call-id
Open

fix(codex): sanitize synthetic heartbeat tool outputs without call_id (#6995)#7002
loulanyue wants to merge 1 commit into
farion1231:mainfrom
loulanyue:fix/6995-codex-heartbeat-missing-call-id

Conversation

@loulanyue

Copy link
Copy Markdown

Summary of Changes

When Codex Desktop runs with thread-targeted heartbeat automation (kind = "heartbeat"), it periodically injects a synthetic heartbeat instruction as a function_call_output item (e.g. name: "automation_update", containing <heartbeat>...\</heartbeat>) without a call_id field.

When forwarding to strict upstreams (such as DeepSeek /responses), this triggered:

  1. HTTP 400: Failed to deserialize the JSON body into the target type: input: missing field call_id
  2. Permanent deadlock for the entire conversation thread because subsequent turns re-send the bad historical item.
  3. In Chat Completions and Anthropic conversions, missing call_id caused empty tool IDs (tool_call_id: "" / tool_use_id: ""), failing upstream validation.

Fix

  1. Responses Passthrough Sanitization: Added sanitize_codex_responses_input_call_ids in codex.rs and invoked it in forwarder.rs for Codex requests. Any synthetic function_call_output / custom_tool_call_output / tool_search_output missing a valid call_id is converted in-place to a standard {"type": "message", "role": "user", "content": [{"type": "input_text", "text": output}]} item.
  2. Chat Completions & Anthropic Defensiveness: In transform_codex_chat.rs and transform_codex_anthropic.rs, any tool output item missing call_id now falls back cleanly to user message / text block, preventing invalid empty tool IDs.
  3. Unit Tests:
    • test_sanitize_codex_responses_input_call_ids_converts_missing_call_id in codex.rs.
    • test_responses_to_chat_completions_synthetic_heartbeat_without_call_id in transform_codex_chat.rs.
    • test_responses_to_anthropic_synthetic_heartbeat_without_call_id in transform_codex_anthropic.rs.

Fixes #6995

@loulanyue
loulanyue requested a review from farion1231 as a code owner August 31, 2026 02:08
@github-actions github-actions Bot added backend Backend (Rust/Tauri) proxy Related to proxy/API forwarding labels Aug 31, 2026
@farion1231

Copy link
Copy Markdown
Owner

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-31T02:13:43.891553Z 113cf9b Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 113cf9bd50

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// byte-for-byte equivalent to the pre-fix conversion.
match item.get("output") {
Some(Value::String(s)) => canonicalize_json_string_if_parseable(s),
if call_id.trim().is_empty() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply the missing-ID fallback to every output type

When Chat Completions conversion receives a custom_tool_call_output or tool_search_output without a nonblank call_id, this new fallback is bypassed because it only covers function_call_output; the sibling branch still emits {"role":"tool","tool_call_id":""}, so strict chat upstreams continue rejecting those requests and replayed history remains unusable. Handle all three output variants consistently, as the native Responses sanitizer and Anthropic conversion now do.

Useful? React with 👍 / 👎.

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

Labels

backend Backend (Rust/Tauri) proxy Related to proxy/API forwarding

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Codex 心跳自动化注入的 function_call_output 缺失 call_id,DeepSeek /responses 400 导致会话永久卡死

2 participants