Repository navigation
Description
Client or integration
Codex CLI
The same endless “Thinking” behavior was first observed in Codex App.
Area
Tools / MCP / web search
Summary
When gpt-5.3-codex-spark is used through OpenCodex, text-only turns complete, but any turn that invokes the Codex exec tool enters an endless loop.
The Codex request uses the Responses Lite additional_tools format and correctly declares a reserved functions namespace containing a custom tool named exec. OpenCodex returns the tool call with both name: "exec" and namespace: "exec". Codex combines those fields into execexec, cannot route that tool, and repeatedly reports:
unsupported custom tool call: execexec
The equivalent native Codex request, with the OpenCodex configuration bypassed using --ignore-user-config, executes pwd successfully and completes. I expected the proxied request to preserve the reserved functions namespace semantics and return the custom exec call without inventing an exec namespace.
This reproduces consistently after upgrading OpenCodex from 2.24.2 to 2.39.0, restarting the service, running ocx sync --restart-codex, and starting a fresh ephemeral task.
Reproduction
-
Configure Codex to use OpenCodex with the native OpenAI forward route.
-
Confirm the current versions:
ocx --version # opencodex 2.39.0 codex --version # codex-cli 0.150.1
-
Restart and resynchronize OpenCodex/Codex:
ocx service restart ocx sync --restart-codex
-
Run a minimal tool-use turn through OpenCodex:
codex exec \ --ephemeral \ --json \ --sandbox read-only \ --skip-git-repo-check \ --model gpt-5.3-codex-spark \ 'Use the shell tool exactly once to execute pwd, then return only the resulting path.'
-
Codex announces the command, then repeatedly logs:
ERROR codex_core::tools::router: error=unsupported custom tool call: execexec -
Run the same request while bypassing the OpenCodex configuration:
codex exec \ --ignore-user-config \ --ephemeral \ --json \ --sandbox read-only \ --skip-git-repo-check \ --model gpt-5.3-codex-spark \ 'Use the shell tool exactly once to execute pwd, then return only the resulting path.'
-
The native request succeeds immediately:
{"type":"item.started","item":{"type":"command_execution","command":"/bin/zsh -lc pwd","status":"in_progress"}} {"type":"item.completed","item":{"type":"command_execution","command":"/bin/zsh -lc pwd","exit_code":0,"status":"completed"}} {"type":"turn.completed"}
Redacted wire evidence
A local pass-through capture between Codex and OpenCodex recorded only protocol types and tool names—no prompts, credentials, headers, account identifiers, or full request bodies.
The inbound Responses Lite request contains an additional_tools item with the expected reserved namespace:
{
"type": "additional_tools",
"tools": [
{
"type": "namespace",
"name": "functions",
"tools": [
{ "type": "custom", "name": "exec" },
{ "type": "function", "name": "wait" },
{ "type": "function", "name": "request_user_input" }
]
}
]
}The response emitted by OpenCodex contains an unexpected namespace equal to the tool name:
{
"type": "response.output_item.added",
"item": {
"type": "custom_tool_call",
"name": "exec",
"namespace": "exec",
"status": "in_progress"
}
}The same incorrect pair is present in response.output_item.done:
{
"type": "response.output_item.done",
"item": {
"type": "custom_tool_call",
"name": "exec",
"namespace": "exec",
"status": "completed"
}
}Codex then reports unsupported custom tool call: execexec and starts another model turn, producing the same invalid call repeatedly.
Version
@bitkyc08/opencodex 2.39.0
Operating system
macOS 27.0 (build 26A5425a), arm64
Provider and model
openai / gpt-5.3-codex-spark
- Adapter:
openai-responses - Authentication mode: native forward route
- Codex CLI: 0.150.1
Logs or error output
ERROR codex_core::tools::router: error=unsupported custom tool call: execexec
ERROR codex_core::tools::router: error=unsupported custom tool call: execexec
ERROR codex_core::tools::router: error=unsupported custom tool call: execexecOpenCodex request logs show the initial Spark response completing upstream with HTTP 200, while Codex rejects the returned client tool call locally.
Screenshots and supporting files
A Codex App task shows “Thinking” indefinitely after announcing that it will inspect the worktree. The minimal CLI reproduction above isolates the same failure without relying on the UI.
Redacted configuration
{
"defaultProvider": "openai",
"providers": {
"openai": {
"adapter": "openai-responses",
"authMode": "forward",
"baseUrl": "https://chatgpt.com/backend-api/codex"
}
}
}Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, personal paths, call IDs, conversation IDs, and full request bodies.
Activity
github-actions commentedon Sep 1, 2026
Issue reopened
The report now contains the information required by the automated check. Thanks for updating it.
lidge-jun commentedon Sep 1, 2026
리뷰 · 우선순위 76 / 80
이 이슈는 Codex CLI/App가 OpenCodex 네이티브 forward(
openai-responses+authMode: forward)로gpt-5.3-codex-spark를 쓸 때, Responses Lite의additional_tools안에 예약 네임스페이스functions로 선언된 커스텀 도구exec이 왕복하면서name: "exec"와 함께namespace: "exec"로 돌아오고, Codex 라우터가 둘을 이어 붙여execexec로 해석한 뒤unsupported custom tool call: execexec를 반복해 턴이 끝나지 않는 버그다. 같은 명령을--ignore-user-config로 OpenCodex를 우회하면pwd가 한 번에 끝나는 대조가 이미 붙어 있어서, 원인은 업스트림 Spark 모델 자체보다 프록시가 Spark용으로 요청 도구 카탈로그를 다시 쓰는 경로에 가깝다. 현재dev(HEADf84dbf91e, package 2.40.0)에서도 재현 골격은 그대로다.현재 체크아웃에서 핵심은
src/adapters/openai-responses.ts의stripSparkCompatibility다. 모델 이름에codex-spark가 들어가면body.tools와input안의additional_tools에 대해type: "namespace"그룹을 풀어서 자식 도구만 위로 올린다. 이슈에 적힌 요청 모양(functions→ customexec+ functionwait/request_user_input)을 forward 어댑터로 돌려 보면, 아웃바운드additional_tools.tools는 예약 그룹이 사라지고 맨 위가{ type: "custom", name: "exec" }인 평면 목록이 된다. 반면src/responses/parser.ts의buildTools와customToolNamespaces는 Codex 0.147 계약을 알아채고functions자식은 네임스페이스 없이 납작하게 두도록 이미 짜여 있다. 즉 파서/가드 쪽은functions를 특수 취급하는데, Spark 호환 스트립만 그 예약을 무시하고 그룹을 해체한다. 네이티브 Codex가 ChatGPT 백엔드로 같은 Lite 모양을 그대로 보내면 성공하니, canonical forward에서는 Spark 스트립이 네임스페이스를 건드리면 안 된다는 쪽이 설득력 있다.응답 쪽도 같이 보면 된다.
buildToolBridgeMaps(src/server/responses/collaboration.ts)는functions에서 풀린exec에 대해toolNsMap항목을 만들지 않는다(로컬에서 동일 입력으로toolNsMap이 비는 것을 확인). 그래서restoreRoutedNamespaceCalls/authorizedBareNamespaceToolAliases가namespace: "functions"를 되살릴 기회도 없고, 업스트림이namespace: "exec"를 실어 오면 그대로 클라이언트로 나간다. Codex는 MCP식namespace+name결합으로execexec를 만들고, 라우터가 거절 → 모델이 같은 호출을 다시 내는 루프가 된다. 텍스트만 있는 턴은 도구 호출이 없어 통과하고,exec가 필요한 턴만 죽는 증상과도 맞다.라인 ~477-570 (
src/adapters/openai-responses.ts·stripSparkCompatibility) - 모델명에codex-spark가 있으면additional_tools안의type: "namespace", name: "functions"까지 자식만 끌어올려 예약 그룹을 없앤다. canonical forward에서도 이 함수가 공통 sanitize 체인에 들어 있어(대략 buildRequest 끝의stripSparkCompatibility(...)호출), ChatGPT가 이해하는 Codex private 모양이 깨진다.라인 ~490-534 (
stripSparkCompatibility네임스페이스 펼치기) - 상위tools배열에서는SPARK_SAFE_TOOL_TYPES에 없는custom을 버리지만, 네임스페이스 자식은 검사 없이filteredInner/flattened에 넣는다. 그래서functions안의 customexec는 “지원 안 함” 주석과 달리 살아남은 채 그룹 정보만 잃는다. Spark 정책(커스텀 드롭 vs 그룹 유지)이 한쪽으로 정리되어 있지 않다.라인 ~221-231 (
src/responses/parser.ts·buildTools) / ~385-388 (customToolNamespaces) - 여기는 반대로functions자식을 네임스페이스 없이 두어 “없는 네임스페이스를 발명하지 말라”고 명시한다. Spark 스트립이 이 계약을 어기면, 요청은 평면 customexec가 되고 응답의 잘못된namespace: "exec"를 고칠 별칭도 없다.심볼
restoreRoutedNamespaceCalls/authorizedBareNamespaceToolAliases(src/responses/namespace-tool-compat.ts,src/server/responses/core.ts) -functions자식은 alias 맵에 안 들어가므로, 클라이언트로 나가는custom_tool_call의namespace: "exec"를 여기서 바로잡지 못한다. 응답 sanitize(예약 builtin이면namespace생략 또는functions로 정규화,namespace === name거부)가 빠져 있다.테스트 공백 -
tests/openai-responses-passthrough.test.ts에 Spark hosted tool 제거 등은 있으나, Responses Liteadditional_tools+functions/exec+gpt-5.3-codex-spark+ forward 조합에서 아웃바운드가 예약 네임스페이스를 유지하는지, 인바운드custom_tool_call이namespace: "exec"로 나가지 않는지를 고정한 회귀가 없다. 이슈의 최소codex exec ... pwd재현을 그 테스트의 계약으로 옮기면 된다.메인테이너의 판단이 필요한 지점
functions네임스페이스만 펼치지 않을 정해야 한다. 전자는 ChatGPT private 모양을 최대한 보존하고, 후자는 Spark가 정말 못 받는 다른 네임스페이스/tool_search만 남긴다.namespace: "exec"를 준 뒤에도 builtinexec/wait등에 대해namespace를 제거하거나functions로 고칠지. 요청 수정만으로 업스트림이 고쳐진다는 증거가 아직 와이어에 없다.dev는 2.40.0이다. Spark 스트립 자체는 HEAD에 남아 있으니 재현 가능성은 높지만, 릴리즈 노트/사용자에게 2.40 재확인을 한 줄 부탁할지는 선택이다.types.ts/config.ts분할 캠페인과는 무관한 런타임 버그라서 close-don't-rebase 대상이 아니다.너의 추천
stripSparkCompatibility에서 예약functions그룹은 펼치지 않거나,isCanonicalOpenAiForwardProvider일 때는 네임스페이스/additional_tools변형을 no-op으로 둔다. 2) 회귀 테스트로 Liteadditional_tools+functions/exec+gpt-5.3-codex-spark+ forward 아웃바운드에name: "functions"네임스페이스가 남는지 단언한다. 3) 가능하면 응답 sanitize로custom_tool_call의namespace === "exec"(또는 namespace===name인 builtin)를 제거하고, 가드로execexec가 클라이언트에 안 나가게 한다. 4) 수정 PR이dev에 닿으면 이 이슈를 닫고, 사용자에게 2.40+ / ocx sync 후 동일pwd재현으로 확인을 요청한다.이 댓글은 grok-bot이 작성했습니다
fix(responses): keep the reserved functions group intact for codex-sp…
lidge-jun commentedon Sep 1, 2026
Fixed on
devby #3224 →d23eab43a. Thank you for the precise wire capture — it pointed straight at the cause.What was happening: for
*codex-spark*models the proxy's Spark compatibility layer flattened everynamespacegroup inadditional_tools, including the reservedfunctionsgroup Codex 0.147+ uses on Responses Lite. Given the flatcustom execdeclaration, the backend answeredcustom_tool_call { name: "exec", namespace: "exec" }, and codex-rs concatenates a non-default namespace intoexecexec. Bypassing the proxy sends the group intact, which is why--ignore-user-configworked. Reproduced locally with ocx 2.40.0 + codex 0.150.1 and traced on both sides of the proxy.The fix keeps the
functionsgroup as a group (MCP-style groups still flatten) and additionally scrubs any tool-call namespace that merely repeats the call's own name on the way back. With the patched proxy the samecodex exec … --model gpt-5.3-codex-sparkturn runspwdand completes.It will ship in the next release; until then
bunx @bitkyc08/opencodex@devor a checkout ofdevcarries it. Closing as landed ondev.docs(devlog): record the pass-1 recount and the #3217 root cause
docs(devlog): record the pass-1 recount and the #3217 root cause
docs(devlog): record the pass-1 recount and the #3217 root cause
docs(devlog): bug-label drawdown campaign record and main->dev regres…
fix(responses): keep the reserved functions group intact for codex-sp…
docs(devlog): bug-label drawdown campaign record and main->dev regres…
fix(responses): keep the reserved functions group intact for codex-sp…
docs(devlog): bug-label drawdown campaign record and main->dev regres…