You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
{"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:
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).
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.
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.
{"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.
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:
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.
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.
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.tsrejects, for all non-passthrough adapters, any request whose parsed history contains a tool-result item without a non-empty stringcall_id. The proxy answers HTTP 400tool 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(namecreate_thread, output = the<codex_delegation>prompt) that carriesid/name/namespace/outputbut nocall_idfield 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-responsespassthrough 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:Response:
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:
usage.jsonl).create_threadwith no model override so the child inherits the routed model.wait_threadsreportsstatus: 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-responsespassthrough unaffected)Logs or error output
usage.jsonlshows 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.tssynthesizescall_{uuid}when a tool call lacks an id). Options, in order of preference:call_idfor 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 literalundefinedreaching the wire).function_call_output.rejectUnpairedToolResults: "repair" | "reject") withrepairas the default.Workaround currently in use: dispatch sub-agents with an explicit passthrough/OpenAI-hosted model override on
create_threaduntil the guard is repaired or made configurable.Happy to re-test any candidate fix against the live proxy; the curl probe above is exact.