Skip to content

[Bug] 2.43.0 unpaired-tool-result guard rejects Codex desktop sub-agent seed shape: every routed-model delegated thread dies instantly with 400 "tool result requires a non-empty string call_id" #3807

Description

@DaveW001

Client or integration

Codex App

Area

Proxy and routing

Summary

The unpaired-tool-result guard added by PR #3471 (for issue #3259) in src/server/responses/core.ts rejects, for all non-passthrough adapters, any request whose parsed history contains a tool-result item without a non-empty string call_id. The proxy answers HTTP 400 tool result requires a non-empty string call_id, so the request never reaches the provider.

Codex desktop seeds every agent-created (sub-agent) thread's first model request with exactly such an item: a lone function_call_output (name create_thread, output = the <codex_delegation> prompt) that carries id/name/namespace/output but no call_id field at all. The identical seed shape appears in both surviving and dead child rollouts across app-server CLI 0.152.0 and 0.153.4.

Net effect behind a local OpenCodex proxy: sub-agents on routed models (opencode-go, zai, meta, google, any translating adapter) die in under one second on their first turn. Retries, resumes, and fresh re-dispatches all reproduce it, so sub-agent delegation is 100% unusable on routed models. Sub-agents on openai-responses passthrough providers are unaffected because the guard is keyed on non-passthrough adapters, which makes the breakage look model-specific from the client side. The same guard also kills a long-running routed child mid-session once its history re-includes the seed item.

Reproduction

Minimal probe, no Codex desktop needed. While any translating-adapter provider is configured, send a tool-result item with no call_id:

curl -s http://127.0.0.1:10101/v1/responses \
  -H 'Content-Type: application/json' \
  -d '{"model":"<any-routed-model>","input":[{"type":"function_call_output","output":"x"}],"max_output_tokens":1}'

Response:

{"error":{"message":"tool result requires a non-empty string call_id","type":"invalid_request_error","code":"invalid_request_error"}}

Adding any non-empty "call_id":"call_x" to the same item returns HTTP 200, so the check is emptiness-only, not actual pairing.

End-to-end steps:

  1. Upgrade the proxy to 2.43.0 from 2.41.0 (the guard is absent in 2.41.0; zero matching 400s on any earlier day in usage.jsonl).
  2. In Codex desktop on a routed-model thread, dispatch a sub-agent via create_thread with no model override so the child inherits the routed model.
  3. The child's first turn fails in under 1 second with the exact 400 above; wait_threads reports status: systemError.

Counted exposure: 90 matching 400s through 2026-09-06 13:36 ET across 8 routed models (qwen3.8-flash 68, glm-5.3 6, muse-spark-1.3-contributor 5, deepseek-v4-pro 3, glm-5.3-flash 2, gemini-3.8-flash 2, deepseek-v4-flash 2, gemini-3.7-flash 2). Control: passthrough-model child threads created in the same window do not hit the error.

Version

2.43.0

Operating system

Windows 11 25H2 (build 26200)

Provider and model

opencode-go / qwen3.8-flash (also zai, meta, google translating adapters; openai-responses passthrough unaffected)

Logs or error output

{"error":{"message":"tool result requires a non-empty string call_id","type":"invalid_request_error","code":"invalid_request_error"}}

usage.jsonl shows the first occurrence about 28 minutes after the 2.41.0 to 2.43.0 self-update on 2026-09-05, then 90 matching entries through 2026-09-06 across 8 routed models. Zero matching occurrences on any earlier day.

Screenshots and supporting files

No response

Redacted configuration

{
  "providers": ["opencode-go", "zai", "meta", "google"],
  "adapter_family": "openai-chat translating adapters",
  "unaffected": "openai-responses passthrough provider",
  "client": "Codex desktop, app-server CLI 0.153.4"
}

Suggested fix. The guard's own comment notes the reverse case is repaired, not rejected (chat/outbound.ts synthesizes call_{uuid} when a tool call lacks an id). Options, in order of preference:

  1. Repair instead of reject on the translating path: synthesize a placeholder call_id for unpaired tool results and optionally log a diagnostic, mirroring the reverse-case repair. This still fixes the actual failure mode from [Provider compatibility] Codex App delegated tasks fail on ollama-cloud with orphan tool result <missing-id> #3259 (a literal undefined reaching the wire).
  2. Or scope the hard rejection to interior unpaired results (a real pairing bug mid-conversation) and tolerate or repair a leading unpaired function_call_output.
  3. Or make it configurable (for example rejectUnpairedToolResults: "repair" | "reject") with repair as the default.

Workaround currently in use: dispatch sub-agents with an explicit passthrough/OpenAI-hosted model override on create_thread until the guard is repaired or made configurable.

Happy to re-test any candidate fix against the live proxy; the curl probe above is exact.

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

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

    bugSomething isn't workingproxyHTTP proxy, routing, reverse-proxy / management auth

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions