Skip to content

fix(responses): keep the reserved functions group intact for codex-spark (#3217) - #3224

Merged
lidge-jun merged 1 commit into
devfrom
codex/260902-i3217-spark-functions-namespace
Sep 1, 2026
Merged

lidge-jun merged 1 commit into
devfrom
codex/260902-i3217-spark-functions-namespace

Conversation

@lidge-jun

Copy link
Copy Markdown
Owner

Summary

Codex 0.147+ on Responses Lite ships every ordinary client tool inside the reserved functions namespace group, carried in an additional_tools input item. stripSparkCompatibility() flattened every namespace group for *codex-spark* models — a rule written when the only groups Codex sent were MCP-style. With the reserved group flattened, the ChatGPT backend answers the code-mode call as custom_tool_call { name: "exec", namespace: "exec" }; codex-rs treats only None/""/"functions" as the default namespace, concatenates that into the unroutable execexec, and re-issues the call every turn. Bypassing the proxy sends the group intact and works.

Traced on a live dev proxy with a tap on both sides: group flattened → namespace:"exec" back, turn loops (25 execexec in 60 s); group intact → bare exec back, pwd runs, turn completes with 0 errors.

  • stripSparkCompatibility keeps a functions group as a group on both body.tools and additional_tools, still filtering its children (tool_search dropped, defer_loading stripped) and admitting custom inside it — exactly what create_tools_json_for_responses_lite sends direct. MCP-style groups flatten as before. Non-Spark models untouched.
  • Belt to that suspender: src/server/responses-self-named-namespace-scrub.ts drops a tool-call namespace that repeats the call's own name on the client-facing passthrough (SSE payload rewrites and the bounded-JSON path). That shape is never a legitimate identity, so no catalog lookup; a genuine mcp__* namespace is untouched.

Fixes #3217.

Verification

  • bun test tests/openai-responses-passthrough.test.ts — new case "keeps the reserved functions group intact for codex-spark, flattens MCP groups ([Bug][2.39.0] Responses Lite exec is returned with namespace "exec", causing an execexec tool-call loop #3217)"; red without the adapter change.
  • bun test tests/responses-self-named-namespace-scrub.test.ts — SSE scrub via live handleResponses, stream:false bounded-JSON scrub, MCP namespace preserved, recursive unit case; red without the scrub module.
  • Focused set (passthrough, scrub, namespace-tool-compat, custom-tool-repair, undeclared-tool-guard): 267 pass / 0 fail. bun run test:changed: 5794 pass / 0 fail across 301 files. bun run typecheck clean; bun run privacy:scan passed.
  • Live: codex exec --model gpt-5.3-codex-spark against a dev proxy from this branch completed pwd (before: 25× unsupported custom tool call: execexec).
  • Full suite deferred to CI (maintainer bypass, tracked after merge).

Checklist

  • Targets dev
  • Focused regression tests next to the existing passthrough tests
  • No docs-site change (restores direct-client behaviour; no user-facing option)
  • No GUI change
  • No new logging of request bodies or identifiers

…ark (#3217)

Codex 0.147+ on Responses Lite ships every ordinary client tool inside the
reserved `functions` namespace group, carried in an `additional_tools` input
item. stripSparkCompatibility() flattened every namespace group for
*codex-spark* models, a rule written when the only groups Codex sent were
MCP-style. With the reserved group flattened the ChatGPT backend answers
the code-mode call as custom_tool_call { name: "exec", namespace: "exec" };
codex-rs treats only None/""/"functions" as the default namespace, so it
concatenates that into the unroutable `execexec` and re-issues the call
every turn. Bypassing the proxy sends the group intact and works.

Traced on a live dev proxy with a tap on both sides: flattened group ->
namespace:"exec" back, turn loops; group intact -> bare `exec` back, pwd
runs, turn completes.

- stripSparkCompatibility keeps a `functions` group as a group, still
  filtering its children (tool_search dropped, defer_loading stripped) and
  admitting `custom` inside it, which is what the direct client sends.
  MCP-style groups are flattened as before.
- Belt to that suspender: scrub a tool-call `namespace` that repeats the
  call's own `name` on the client-facing passthrough (SSE and bounded
  JSON). That shape is never a legitimate identity, so no catalog lookup.

Fixes #3217.
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 1, 2026 21:46
@lidge-jun lidge-jun added the bug Something isn't working label Sep 1, 2026
@lidge-jun
lidge-jun merged commit d23eab4 into dev Sep 1, 2026
6 of 7 checks passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 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-09-01T21:51:53.049884Z 21b73c2 PR opened
ℹ️ 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.

@lidge-jun
lidge-jun deleted the codex/260902-i3217-spark-functions-namespace branch September 1, 2026 21:46
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@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: 21b73c22b9

ℹ️ 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".

Comment on lines +36 to +40
if (
(value.type === "custom_tool_call" || value.type === "function_call")
&& typeof value.name === "string"
&& value.name.length > 0
&& value.namespace === value.name

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 Restrict scrubbing to malformed Spark calls

When a caller legitimately declares a namespace and child with the same name (for example, namespace mcp__worker containing function mcp__worker), this unconditional equality check removes the namespace from every provider/model's streamed and bounded passthrough response. The client then receives a different bare-tool identity; depending on the catalog, the undeclared-tool guard either rejects the response or permits dispatch to the wrong bare tool. Gate this repair on the affected Spark route and verify that the bare tool was actually declared instead of assuming every self-named pair is malformed.

AGENTS.md reference: src/AGENTS.md:L19-L19

Useful? React with 👍 / 👎.

Comment on lines +518 to +521
if (isPlainObject(t) && t.type === "namespace" && t.name === SPARK_RESERVED_FUNCTIONS_NAMESPACE) {
const kept = filterSparkFunctionsGroup(t);
if (kept !== t) changed = true;
if (kept) flattened.push(kept);

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 Normalize function schemas inside the preserved group

When a functions group contains a function whose parameters is absent or not an object schema, preserving the group here prevents the later normalizeToolSchemas pass from repairing it because that pass only examines direct entries in body.tools and additional_tools.tools. Before this change the group was flattened, so the child received the required { type: "object" } normalization; now the malformed nested declaration reaches Spark and can make the upstream reject the entire request. Apply the same schema normalization recursively to retained group children.

AGENTS.md reference: src/AGENTS.md:L19-L19

Useful? React with 👍 / 👎.

@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 77 / 80

이 PR은 Codex 0.147+ Responses Lite에서 codex-spark 모델이 execexec 미등록 도구 루프에 빠지던 버그(#3217)를 고칩니다. 지금 dev HEAD는 이미 이 커밋 d23eab43a 이고, package는 2.40.0입니다. 증상은 단순합니다. 클라이언트가 셸 exec 를 내면, 프록시를 거친 뒤 백엔드가 custom_tool_call { name: "exec", namespace: "exec" } 형태로 돌려주고, codex-rs는 기본 네임스페이스로 None/""/functions 만 인정해서 이름을 execexec 로 이어 붙입니다. 매칭되는 도구가 없으니 같은 호출을 매 턴 다시 보냅니다. 본문에 적힌 대로 라이브 프록시에서 60초 동안 25번 반복된 사례가 있었고, 프록시를 우회하면 같은 백엔드가 bare exec 를 돌려주어 pwd 가 정상 끝났습니다.

원인은 src/adapters/openai-responses.ts 의 stripSparkCompatibility 가 Spark용으로 모든 type: "namespace" 그룹을 납작하게 펼치던 규칙입니다. 예전에는 Codex가 MCP 스타일 그룹만 보냈기 때문에 그 규칙이 맞았습니다. 0.147+ 는 일반 클라이언트 도구를 예약 그룹 name: "functions" 안에 넣고, 그걸 tools 와 additional_tools 입력 항목에 같이 실어 보냅니다. 그 예약 그룹까지 펼치면 백엔드 응답 모양이 바뀌어 위 루프가 납니다. 이 PR은 예약 functions 그룹만 그룹으로 남기고, 안의 tool_search 는 버리고, defer_loading 은 빼며, custom (코드모드 exec) 은 그룹 안에서 허용합니다. MCP 스타일 그룹은 예전처럼 펼칩니다. Spark가 아닌 모델은 손대지 않습니다.

벨트 역할로 src/server/responses-self-named-namespace-scrub.ts 가 새로 생겼습니다. 클라이언트에 나가는 SSE 페이로드와 stream:false 바운디드 JSON 경로에서, custom_tool_call / function_call 의 namespace 가 자기 name 과 같으면 그 필드를 지웁니다. MCP처럼 mcp__* 같은 진짜 네임스페이스는 건드리지 않습니다. src/server/responses/core.ts 가 image-gen restore → self-named scrub → routed namespace restore 순으로 묶습니다. 어댑터 쪽 원인 제거가 본방이고, 스크럽은 혹시 남을 잘못된 응답을 막는 보조입니다.

검증은 탄탄합니다. tests/openai-responses-passthrough.test.ts 에 예약 그룹 유지 + MCP 펼침 케이스가 추가됐고, tests/responses-self-named-namespace-scrub.test.ts 가 SSE·JSON·MCP 보존·재귀 단위를 덮습니다. 본문 기준 focused 267 / test:changed 5794 통과, typecheck·privacy:scan 통과, 라이브 codex exec --model gpt-5.3-codex-spark 로 pwd 완료를 확인했습니다. types.ts / config.ts 분할 캠페인과는 무관합니다. 닫고 다시 쌓을 대상이 아닙니다. 이미 dev 에 머지됐고 #3217 도 닫혔습니다. 후속으로 열린 #3226 은 이 스크럽을 “이번 턴이 승인한 bare custom만”으로 더 좁히려는 작업입니다.

라인 476 (src/adapters/openai-responses.ts · stripSparkCompatibility 주석) - 상단 bullet이 여전히 “Drops unsupported tool types (tool_search, custom)”라고 적혀 있습니다. 이번 변경 뒤로는 예약 functions 그룹 안의 custom 은 유지되고, 탑레벨/펼친 쪽만 버립니다. 주석만 옛 규칙과 어긋납니다. 읽는 사람이 다시 헷갈릴 수 있으니 한 줄로 고쳐 두면 좋습니다.
라인 518-521 / 562-565 (stripSparkCompatibility · functions 분기) - t.name === SPARK_RESERVED_FUNCTIONS_NAMESPACE 문자열 비교로 예약 그룹만 지킵니다. 이름은 상수 "functions" 한곳(라인 614)에 모여 있어서 좋습니다. 다만 그룹 tools 가 없거나 필터 후 비면 undefined 로 그룹 전체를 빼 버립니다. Codex가 빈 예약 그룹을 보내는 경우는 드물지만, 그 경우 업스트림에 그룹이 아예 안 갑니다.
라인 522-526 / 566-570 (MCP 스타일 펼침) - 예약이 아닌 namespace는 예전처럼 자식을 그대로 탑레벨에 올립니다. 펼친 뒤 SPARK_SAFE_TOOL_TYPES 재필터는 탑레벨 분기에서만 돌아가고, MCP 자식으로 들어온 custom 등은 이 루프에서 바로 flattened 에 들어갑니다. 이번 PR이 새로 만든 구멍은 아니고 기존 펼침 동작입니다. Spark+MCP+custom 조합을 실제로 쓰는지 한 번만 확인하면 충분합니다.
라인 36-44 (src/server/responses-self-named-namespace-scrub.ts · scrubSelfNamedToolCallNamespace) - namespace === name 이고 name이 비어 있지 않으면 무조건 지웁니다. 카탈로그를 보지 않습니다. #3217 루프를 막는 데는 맞고, 동명 네임스페이스 도구가 진짜로 선언된 턴에서는 과잉일 수 있습니다. 그 구멍은 이미 #3226 이 좁히려고 열려 있습니다.
라인 48-57 (scrubSelfNamedToolCallNamespaceInJson) - "namespace" 문자열이 본문에 없으면 파싱을 건너뜁니다. 빠른 경로로 좋습니다. JSON이 아니면 원문을 그대로 돌려서 SSE 조각이 깨져도 프록시가 죽지 않습니다.
라인 4649 / 4881-4882 (src/server/responses/core.ts) - SSE rewrite와 bounded JSON이 같은 스크럽을 씁니다. 경로가 갈라지지 않은 점이 좋습니다. 순서는 image-gen restore 다음, routed namespace restore 앞입니다.
테스트 keeps the reserved functions group intact for codex-spark, flattens MCP groups (#3217) - tools 와 additional_tools 양쪽에 같은 기대를 걸어 두었습니다. tool_search 삭제와 defer_loading 제거까지 한 케이스에 들어 있어 회귀 잠금이 분명합니다.
심볼 SPARK_RESERVED_FUNCTIONS_NAMESPACE - 함수 정의보다 아래에 있지만 모듈 로드 후 호출 시점에 이미 초기화되므로 런타임 문제는 없습니다. 읽기 순서만 위아래로 맞춰 두면 더 편합니다.

메인테이너의 판단이 필요한 지점

  • 이미 dev 에 머지된 상태이므로, 이 리뷰는 기록용입니다. 롤백할 이유는 없어 보입니다.
  • 주석 라인 476의 “drop custom” 문구를 후속 정리 커밋에서 고칠지, fix(responses): scope self-named namespace scrub #3226 과 같이 묶을지.
  • self-named 스크럽을 fix(responses): scope self-named namespace scrub #3226 처럼 “이번 턴 승인 bare custom”으로 좁힐지, 당분간 무조건 벨트를 유지할지.
  • MCP 펼침 경로에서 자식 custom 을 Spark 안전 타입으로 한 번 더 걸러야 할 실사용 사례가 있는지.
  • types/config 분할과 무관하니 이 PR/후속을 분할 때문에 닫을 이유는 없습니다.

너의 추천
올바른 원인 수정입니다. 예약 functions 그룹을 살리는 쪽이 본방이고, self-named 스크럽은 보조로 잘 맞습니다. 라이브 재현·회귀 테스트·양 경로(SSE/JSON) 배선까지 갖춰져 있어 dev 에 둔 판단은 타당합니다. 남은 일은 (1) 주석의 custom drop 문구를 실제 동작에 맞게 고치고, (2) #3226 으로 스크럽 범위를 안전하게 좁힐지 결정하는 것입니다. 이 PR 자체를 되돌리거나 닫을 필요는 없습니다.

이 댓글은 grok-bot이 작성했습니다

lidge-jun pushed a commit that referenced this pull request Sep 2, 2026
lidge-jun pushed a commit that referenced this pull request Sep 2, 2026
lidge-jun added a commit that referenced this pull request Sep 2, 2026
…sion audit (#3218)

* docs(devlog): open the bug/PR closeout stack roadmap

* docs(devlog): fold the A-gate import-boundary finding into phase 5

* docs(devlog): record the #3163 and #3166 landings

* docs(devlog): record why #2986 does not land in this train

* docs(devlog): close out the bug/PR closeout stack

* docs(devlog): record the final green CI verdict on dev

* docs(devlog): open the bug-label drawdown roadmap with audit corrections

* docs(devlog): record the Batch A landings and first rebase carry

* docs(devlog): record the Batch B rebase carries

* docs(devlog): record why the rebase service earned its keep

* docs(devlog): record the Batch C rebases and the one real review finding

* docs(devlog): record the #2999 scope boundary that survived execution

* docs(devlog): record Batch D - every bug PR closed

* docs(devlog): record what the PR half of the campaign cost

* docs(devlog): replan the remaining issues to one per cycle

* docs(devlog): carry the i3141 evidence into the replan

* docs(devlog): diagnose i3141 - fix predates the reported version

* docs(devlog): retire the second bundle

* docs(devlog): record the i3141 re-triage action and outcome

* docs(devlog): diagnose i3152 log table jitter

* docs(devlog): i3152 - measurement disproved the layout diagnosis

* docs(devlog): diagnose i3136 slashed-id price lookup

* docs(devlog): diagnose i3150 citation marker passthrough

* docs(devlog): diagnose i3155 capacity plan allowlist

* docs(devlog): i1419 stays open pending crash frames

* docs(devlog): record the i1419 re-triage ask

* docs(devlog): diagnose i2999 publication overwrite race

* docs(devlog): record the i2999 outcome and remaining scope

* docs(devlog): diagnose i2813 as a client-side reserve gate

* docs(devlog): diagnose i1527 residuals as trace-blocked

* docs(devlog): correct i1527 envelope-cap wording (192 blobs, HTTP 400)

* docs(devlog): plan p3193 loopback alpha-search reimplementation

* docs(devlog): record p3193 landing (#3205 -> 53c09a2)

* docs(devlog): plan the main->dev regression audit

* docs(devlog): pin regaudit counts, add tests-only/security passes and the exact-head dispatch

* docs(devlog): record regaudit reviewer verdicts

* docs(devlog): record the exact-head dev CI verdict and Windows classification

* docs(devlog): record the main control run proving the Windows failures predate the range

* docs(devlog): record the pass-1 recount and the #3217 root cause

* docs(devlog): plan i3217 (Spark functions-namespace flattening)

* docs(devlog): record i3217 landing (#3224 -> d23eab4)

* docs(devlog): regaudit2 recount and disposition table

* docs(devlog): regaudit2 CI verdict on d23eab4 and the four PR arrivals

* docs(devlog): plan p3226 (scoped namespace scrub)

* docs(devlog): p3226 audit finding and carry plan

* docs(devlog): record p3226 landing (#3234 -> b732b0d)

* docs(devlog): plan p3227 (combo zero-output incomplete failover)

* docs(devlog): record p3227 landing

* docs(devlog): plan p3228 (encrypted V2 spawn native fallback)

* docs(devlog): record p3228 landing

* docs(devlog): plan p3229 (Codexless originator in task recovery)

* docs(devlog): record p3229 landing and the #3239 regression repair

* docs(devlog): r3239 regression repair record

* docs(devlog): r3239 audit note

* docs(devlog): record p3232 (merged by maintainer)

* docs(devlog): p3232 verification result

* docs(devlog): regaudit3 recount and landing table

* docs(devlog): record the #3239/#3240 revert and correct the #3228 disposition

* docs(devlog): rv3239 revert record

* docs(devlog): rv3239 audit note

* docs(devlog): regaudit3 second-dispatch verdict

* docs(devlog): regaudit3 recount refreshed (#1419 closed by maintainer; count 4)

* docs(devlog): regaudit3 final CI verdict and c-7

---------

Co-authored-by: jun <jun@lidge.dev>
tarunravi pushed a commit to tarunravi/opencodex that referenced this pull request Sep 14, 2026
…ark (lidge-jun#3217) (lidge-jun#3224)

Codex 0.147+ on Responses Lite ships every ordinary client tool inside the
reserved `functions` namespace group, carried in an `additional_tools` input
item. stripSparkCompatibility() flattened every namespace group for
*codex-spark* models, a rule written when the only groups Codex sent were
MCP-style. With the reserved group flattened the ChatGPT backend answers
the code-mode call as custom_tool_call { name: "exec", namespace: "exec" };
codex-rs treats only None/""/"functions" as the default namespace, so it
concatenates that into the unroutable `execexec` and re-issues the call
every turn. Bypassing the proxy sends the group intact and works.

Traced on a live dev proxy with a tap on both sides: flattened group ->
namespace:"exec" back, turn loops; group intact -> bare `exec` back, pwd
runs, turn completes.

- stripSparkCompatibility keeps a `functions` group as a group, still
  filtering its children (tool_search dropped, defer_loading stripped) and
  admitting `custom` inside it, which is what the direct client sends.
  MCP-style groups are flattened as before.
- Belt to that suspender: scrub a tool-call `namespace` that repeats the
  call's own `name` on the client-facing passthrough (SSE and bounded
  JSON). That shape is never a legitimate identity, so no catalog lookup.

Fixes lidge-jun#3217.

Co-authored-by: jun <jun@lidge.dev>
tarunravi pushed a commit to tarunravi/opencodex that referenced this pull request Sep 14, 2026
…sion audit (lidge-jun#3218)

* docs(devlog): open the bug/PR closeout stack roadmap

* docs(devlog): fold the A-gate import-boundary finding into phase 5

* docs(devlog): record the lidge-jun#3163 and lidge-jun#3166 landings

* docs(devlog): record why lidge-jun#2986 does not land in this train

* docs(devlog): close out the bug/PR closeout stack

* docs(devlog): record the final green CI verdict on dev

* docs(devlog): open the bug-label drawdown roadmap with audit corrections

* docs(devlog): record the Batch A landings and first rebase carry

* docs(devlog): record the Batch B rebase carries

* docs(devlog): record why the rebase service earned its keep

* docs(devlog): record the Batch C rebases and the one real review finding

* docs(devlog): record the lidge-jun#2999 scope boundary that survived execution

* docs(devlog): record Batch D - every bug PR closed

* docs(devlog): record what the PR half of the campaign cost

* docs(devlog): replan the remaining issues to one per cycle

* docs(devlog): carry the i3141 evidence into the replan

* docs(devlog): diagnose i3141 - fix predates the reported version

* docs(devlog): retire the second bundle

* docs(devlog): record the i3141 re-triage action and outcome

* docs(devlog): diagnose i3152 log table jitter

* docs(devlog): i3152 - measurement disproved the layout diagnosis

* docs(devlog): diagnose i3136 slashed-id price lookup

* docs(devlog): diagnose i3150 citation marker passthrough

* docs(devlog): diagnose i3155 capacity plan allowlist

* docs(devlog): i1419 stays open pending crash frames

* docs(devlog): record the i1419 re-triage ask

* docs(devlog): diagnose i2999 publication overwrite race

* docs(devlog): record the i2999 outcome and remaining scope

* docs(devlog): diagnose i2813 as a client-side reserve gate

* docs(devlog): diagnose i1527 residuals as trace-blocked

* docs(devlog): correct i1527 envelope-cap wording (192 blobs, HTTP 400)

* docs(devlog): plan p3193 loopback alpha-search reimplementation

* docs(devlog): record p3193 landing (lidge-jun#3205 -> 53c09a2)

* docs(devlog): plan the main->dev regression audit

* docs(devlog): pin regaudit counts, add tests-only/security passes and the exact-head dispatch

* docs(devlog): record regaudit reviewer verdicts

* docs(devlog): record the exact-head dev CI verdict and Windows classification

* docs(devlog): record the main control run proving the Windows failures predate the range

* docs(devlog): record the pass-1 recount and the lidge-jun#3217 root cause

* docs(devlog): plan i3217 (Spark functions-namespace flattening)

* docs(devlog): record i3217 landing (lidge-jun#3224 -> d23eab4)

* docs(devlog): regaudit2 recount and disposition table

* docs(devlog): regaudit2 CI verdict on d23eab4 and the four PR arrivals

* docs(devlog): plan p3226 (scoped namespace scrub)

* docs(devlog): p3226 audit finding and carry plan

* docs(devlog): record p3226 landing (lidge-jun#3234 -> b732b0d)

* docs(devlog): plan p3227 (combo zero-output incomplete failover)

* docs(devlog): record p3227 landing

* docs(devlog): plan p3228 (encrypted V2 spawn native fallback)

* docs(devlog): record p3228 landing

* docs(devlog): plan p3229 (Codexless originator in task recovery)

* docs(devlog): record p3229 landing and the lidge-jun#3239 regression repair

* docs(devlog): r3239 regression repair record

* docs(devlog): r3239 audit note

* docs(devlog): record p3232 (merged by maintainer)

* docs(devlog): p3232 verification result

* docs(devlog): regaudit3 recount and landing table

* docs(devlog): record the lidge-jun#3239/lidge-jun#3240 revert and correct the lidge-jun#3228 disposition

* docs(devlog): rv3239 revert record

* docs(devlog): rv3239 audit note

* docs(devlog): regaudit3 second-dispatch verdict

* docs(devlog): regaudit3 recount refreshed (lidge-jun#1419 closed by maintainer; count 4)

* docs(devlog): regaudit3 final CI verdict and c-7

---------

Co-authored-by: jun <jun@lidge.dev>
agentHits pushed a commit to agentHits/opencodex that referenced this pull request Sep 17, 2026
…ark (lidge-jun#3217) (lidge-jun#3224)

Codex 0.147+ on Responses Lite ships every ordinary client tool inside the
reserved `functions` namespace group, carried in an `additional_tools` input
item. stripSparkCompatibility() flattened every namespace group for
*codex-spark* models, a rule written when the only groups Codex sent were
MCP-style. With the reserved group flattened the ChatGPT backend answers
the code-mode call as custom_tool_call { name: "exec", namespace: "exec" };
codex-rs treats only None/""/"functions" as the default namespace, so it
concatenates that into the unroutable `execexec` and re-issues the call
every turn. Bypassing the proxy sends the group intact and works.

Traced on a live dev proxy with a tap on both sides: flattened group ->
namespace:"exec" back, turn loops; group intact -> bare `exec` back, pwd
runs, turn completes.

- stripSparkCompatibility keeps a `functions` group as a group, still
  filtering its children (tool_search dropped, defer_loading stripped) and
  admitting `custom` inside it, which is what the direct client sends.
  MCP-style groups are flattened as before.
- Belt to that suspender: scrub a tool-call `namespace` that repeats the
  call's own `name` on the client-facing passthrough (SSE and bounded
  JSON). That shape is never a legitimate identity, so no catalog lookup.

Fixes lidge-jun#3217.

Co-authored-by: jun <jun@lidge.dev>
agentHits pushed a commit to agentHits/opencodex that referenced this pull request Sep 17, 2026
…sion audit (lidge-jun#3218)

* docs(devlog): open the bug/PR closeout stack roadmap

* docs(devlog): fold the A-gate import-boundary finding into phase 5

* docs(devlog): record the lidge-jun#3163 and lidge-jun#3166 landings

* docs(devlog): record why lidge-jun#2986 does not land in this train

* docs(devlog): close out the bug/PR closeout stack

* docs(devlog): record the final green CI verdict on dev

* docs(devlog): open the bug-label drawdown roadmap with audit corrections

* docs(devlog): record the Batch A landings and first rebase carry

* docs(devlog): record the Batch B rebase carries

* docs(devlog): record why the rebase service earned its keep

* docs(devlog): record the Batch C rebases and the one real review finding

* docs(devlog): record the lidge-jun#2999 scope boundary that survived execution

* docs(devlog): record Batch D - every bug PR closed

* docs(devlog): record what the PR half of the campaign cost

* docs(devlog): replan the remaining issues to one per cycle

* docs(devlog): carry the i3141 evidence into the replan

* docs(devlog): diagnose i3141 - fix predates the reported version

* docs(devlog): retire the second bundle

* docs(devlog): record the i3141 re-triage action and outcome

* docs(devlog): diagnose i3152 log table jitter

* docs(devlog): i3152 - measurement disproved the layout diagnosis

* docs(devlog): diagnose i3136 slashed-id price lookup

* docs(devlog): diagnose i3150 citation marker passthrough

* docs(devlog): diagnose i3155 capacity plan allowlist

* docs(devlog): i1419 stays open pending crash frames

* docs(devlog): record the i1419 re-triage ask

* docs(devlog): diagnose i2999 publication overwrite race

* docs(devlog): record the i2999 outcome and remaining scope

* docs(devlog): diagnose i2813 as a client-side reserve gate

* docs(devlog): diagnose i1527 residuals as trace-blocked

* docs(devlog): correct i1527 envelope-cap wording (192 blobs, HTTP 400)

* docs(devlog): plan p3193 loopback alpha-search reimplementation

* docs(devlog): record p3193 landing (lidge-jun#3205 -> 144ddf4)

* docs(devlog): plan the main->dev regression audit

* docs(devlog): pin regaudit counts, add tests-only/security passes and the exact-head dispatch

* docs(devlog): record regaudit reviewer verdicts

* docs(devlog): record the exact-head dev CI verdict and Windows classification

* docs(devlog): record the main control run proving the Windows failures predate the range

* docs(devlog): record the pass-1 recount and the lidge-jun#3217 root cause

* docs(devlog): plan i3217 (Spark functions-namespace flattening)

* docs(devlog): record i3217 landing (lidge-jun#3224 -> fe855b3)

* docs(devlog): regaudit2 recount and disposition table

* docs(devlog): regaudit2 CI verdict on fe855b3 and the four PR arrivals

* docs(devlog): plan p3226 (scoped namespace scrub)

* docs(devlog): p3226 audit finding and carry plan

* docs(devlog): record p3226 landing (lidge-jun#3234 -> 827456e)

* docs(devlog): plan p3227 (combo zero-output incomplete failover)

* docs(devlog): record p3227 landing

* docs(devlog): plan p3228 (encrypted V2 spawn native fallback)

* docs(devlog): record p3228 landing

* docs(devlog): plan p3229 (Codexless originator in task recovery)

* docs(devlog): record p3229 landing and the lidge-jun#3239 regression repair

* docs(devlog): r3239 regression repair record

* docs(devlog): r3239 audit note

* docs(devlog): record p3232 (merged by maintainer)

* docs(devlog): p3232 verification result

* docs(devlog): regaudit3 recount and landing table

* docs(devlog): record the lidge-jun#3239/lidge-jun#3240 revert and correct the lidge-jun#3228 disposition

* docs(devlog): rv3239 revert record

* docs(devlog): rv3239 audit note

* docs(devlog): regaudit3 second-dispatch verdict

* docs(devlog): regaudit3 recount refreshed (lidge-jun#1419 closed by maintainer; count 4)

* docs(devlog): regaudit3 final CI verdict and c-7

---------

Co-authored-by: jun <jun@lidge.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant