Skip to content

fix(responses): normalize invented default. namespace back to the declared bare tool - #4264

Merged
lidge-jun merged 3 commits into
codex/4167-claude-desktop-delete-unavailablefrom
codex/4176-default-namespace-bare-tool
Sep 11, 2026
Merged

fix(responses): normalize invented default. namespace back to the declared bare tool#4264
lidge-jun merged 3 commits into
codex/4167-claude-desktop-delete-unavailablefrom
codex/4176-default-namespace-bare-tool

Conversation

@lidge-jun

Copy link
Copy Markdown
Owner

Summary

Carries #4181 by @chilung-cgu so #4176 lands in this release train. The diff is that branch's three commits cherry-picked unchanged.

A routed non-OpenAI provider invents a default. namespace for a tool the client declared bare. Codex declares view_image; the provider emits default.view_image; the undeclared-tool guard sees a name that is not in the caller's catalog and aborts the stream. The user loses the turn for a tool they did declare.

The fix does not loosen the guard. It teaches the request path which bare names the caller actually declared — collectDeclaredBareWireToolNames threads a declaredBareWireToolNames set alongside the existing declaredWireToolNames — and then normalizes default.<name> back to <name> only when <name> is genuinely in that declared bare set. A default. prefix on something the caller never declared is still rejected exactly as before. Provenance is tracked so a continuation cannot widen the caller snapshot.

The normalization is applied on the replay path and on the streamed client JSON, including function_call_arguments.done, which is what makes a 2-turn continuation relay the same identity the caller sent.

Verification

  • Diff verified identical to fix(tools): normalize invented default. prefix back to declared bare tool #4181's dev...head range (5 files, +793/-19).
  • Carried tests: tests/responses/responses-undeclared-tool-guard.test.ts (+459) and tests/adapters/bridge-legacy-shell-normalization.test.ts (+10).
  • bun test tests/lab/core-lab-boundary.test.ts could not complete locally — a concurrent test run in another worktree holds the Bun user lock. This PR touches src/server/responses/core.ts, so that guard is the one to watch in CI; the new imports come from responses-undeclared-tool-guard, which core.ts already imported, so no new module reach is introduced.
  • bun run typecheck and the full suite are left to CI on Linux, Windows, and macOS.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Closes #4176
Supersedes #4181

Stacked on #4263. Base retargets to dev once the parent lands.

Co-authored-by: chilung b0423031@gmail.com

… track bare provenance

- Downstream identity normalization in Responses relay: rewrite provider-invented
  default. prefix or namespace: "default" back to declared bare tool for SSE streams
  (added, done, terminal completed/incomplete snapshots) and non-streaming JSON
  responses, preserving all item fields (id, call_id, arguments).
- Bare tool provenance tracking: collectDeclaredBareWireToolNames collects top-level
  and builtin functions namespace declarations that do not carry . or __, preventing
  declarations like foo__view_image from authorizing default.view_image or
  { namespace: "default", name: "view_image" }.
- Shared normalization helper: update normalizeDeclaredToolName and guard helper
  docstrings to clarify default namespace normalization boundary beyond code-mode
  exec helpers.
- Test coverage in tests/responses/responses-undeclared-tool-guard.test.ts covering
  bare wire collection, SSE stream item rewriting, terminal snapshot rewriting, JSON
  rewriting, negative controls for declared namespaced tools, and preservation of
  explicitly declared default. and default__ tools.
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 11, 2026 09:47
@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • ^dev$
  • ^preview$

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 6bd3cd78-b86a-4038-b493-1fa63a0bc29f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Sep 11, 2026
@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 70 / 80

이 PR은 Codex 앱이 비-OpenAI 모델로 라우팅할 때, 클라이언트가 맨 이름으로 선언한 도구(view_image)를 프로바이더가 default.view_image(또는 namespace: "default")로 돌려보내면 undeclared-tool 가드가 스트림을 끊어 버리는 버그(#4176)를 고칩니다. 지금 dev HEAD(b550d24e1)의 src/server/responses-undeclared-tool-guard.ts는 요청에 선언된 와이어 이름만 허용하고, default. 접두가 붙은 이름은 별개 도구로 봅니다. 그래서 사용자는 자기가 선언한 도구인데도 routed provider emitted undeclared client tool "default.view_image"로 턴을 잃습니다. #3403이 고친 건 default__apply_patchdefault.apply_patch처럼 이미 네임스페이스가 있는 선언의 표기 차이였고, 이번 건은 맨 이름 선언에 프로바이더가 가짜 default 네임스페이스를 붙인 다른 케이스입니다.

고치는 방식은 가드를 느슨하게 풀지 않습니다. collectDeclaredBareWireToolNames로 맨 이름(점/__ 없는 top-level·builtin functions 그룹)만 따로 모으고, declaredBareWireToolNames를 기존 declaredWireToolNames 옆에 같이 실어 보냅니다. 그다음 default.<이름> 또는 namespace: "default"그 맨 이름이 진짜 선언되어 있고, default.<이름>/default__<이름> 자체는 선언되지 않았을 때만 맨 이름으로 되돌립니다. 선언하지 않은 default.는 예전처럼 거절합니다. 재생(replay) 경로·스트리밍 SSE·비스트리밍 JSON·function_call_arguments.done까지 같은 정규화를 태워서, 2턴 이어가기에서도 호출자가 보낸 정체성이 유지됩니다. 연속 요청이 과거 카탈로그로 bare 스냅샷을 넓히지 못하도록 core.ts에서 clear 후 현재 호출자 스냅샷을 다시 채웁니다.

파일은 다섯 개(+793/−19)입니다. src/types/tools.tsnormalizeDeclaredToolName에 선택 인자 declaredBare가 추가되어 브리지 경로(bridge.ts는 세 번째 인자 없이 호출)도 fallback으로 같은 규칙을 타게 했고, 테스트에 브리지 케이스 하나가 붙었습니다. 본문은 @chilung-cgu#4181 세 커밋을 그대로 실은 캐리이고, #4176을 닫고 #4181을 대체한다고 적혀 있습니다. 테스트(tests/responses/responses-undeclared-tool-guard.test.ts +459)가 bare 수집·SSE rewrite·터미널 스냅샷·JSON·네거티브(진짜 선언된 default./namespaced)·2턴 이어가기까지 넓게 잡혀 있어, 가드 변경치고 검증이 두텁습니다. CI 리눅스 샤드·gates·hygiene는 이미 그린이고 macOS만 아직 도는 중입니다. 현재 dev 방향(허브 로케일·클라이언트 readiness·풀 귀속)과 types/config 분할에 걸려 닫을 대상은 아닙니다. 다만 베이스가 #4263(Claude Desktop unavailable 삭제)이고 그 위가 #4262(zai 쿼터)라, 응답 도구 정규화와 무관한 스택에 묶여 있습니다.

경로 src/server/responses-undeclared-tool-guard.ts · collectDeclaredBareWireToolNames - 맨 이름만 모읍니다. foo__view_imagefoo.tool는 bare 집합에 안 들어가서 default.view_image를 허가하지 않습니다. 의도에 맞습니다.
경로 undeclaredNameInItem / normalizeDefaultNamespaceInItem - namespace === "default"일 때 맨 이름으로 되돌리고 namespace 필드를 지웁니다. default.view_image 접두 형태도 같이 처리합니다. 명시적으로 default.view_image/default__view_image를 선언한 요청은 정규화하지 않고 그대로 둡니다.
경로 createUndeclaredToolCallGuardBlockRewrite - 먼저 undeclared로 끊고, 통과한 뒤에만 SSE payload를 rewrite합니다. 가드 우선순위가 맞습니다. function_call_arguments.done도 검사·정규화 대상에 넣었습니다.
경로 src/server/responses/core.ts · declaredBareWireToolNames - replay prefix가 0일 때만 outbound bare를 넣고, 항상 현재 호출자 bare를 합칩니다. 연속 턴이 과거 카탈로그로 bare를 넓히지 못하게 한 점이 #4176 안전 조건과 맞습니다.
경로 src/types/tools.ts · normalizeDeclaredToolName - declaredBare를 생략하면 declared로 fallback합니다. 브리지 테스트는 맨 이름만 든 Set이라 동작하지만, 브리지에 dotted/flattened 이름이 섞인 카탈로그를 넘기면 bare 출처가 흐려질 수 있습니다. Responses 경로는 전용 bare 집합을 쓰므로 실사용 핫패스는 안전합니다.
경로 tests/.../responses-undeclared-tool-guard.test.ts - namespace: 'default' 케이스 한 줄 들여쓰기가 어긋나 있습니다(동작에는 영향 없음). 네거티브·명시 default 선언 보존 케이스가 있어 회귀 방어는 충분합니다.
경로 PR 베이스 - #4263/#4262와 기능이 무관합니다. 스택만 때문에 머지 순서가 묶입니다. 체리픽 내용 자체는 dev에 독립 착지 가능해 보입니다.

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

너의 추천
macOS CI만 마무리되면 내용 기준으로는 머지해도 됩니다. 스택 부모가 아직 열려 있으니 #4262·#4263이 dev에 들어간 뒤 베이스를 dev로 맞추거나, 이 세 커밋만 dev에 다시 올리는 쪽이 랜딩이 더 빠릅니다. 머지 직후 #4176을 닫고 leftover #4181에는 Landed via #4264 at <commit> + landed-via-maintainer로 닫으세요. types/config 분할에 걸려 닫을 대상은 아닙니다.

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

@lidge-jun

Copy link
Copy Markdown
Owner Author

Integrating under the dev-only maintainer bypass in MAINTAINERS.md (admin access; no second approval required for dev).

Exact-head CI evidence — head 4a2ee627: every check run SUCCESS/SKIPPED and CodeRabbit SUCCESS; nothing failing or pending.

Layer 3 of the manual chain. Parent #4263 landed; auto-retargeted to dev.

@lidge-jun
lidge-jun merged commit 391e40d into codex/4167-claude-desktop-delete-unavailable Sep 11, 2026
27 checks passed
@lidge-jun
lidge-jun deleted the codex/4176-default-namespace-bare-tool branch September 11, 2026 11:43
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.

2 participants