Skip to content

[bug] routed provider prefixes bare Codex tool with default.: default.view_image rejected as undeclared #4176

Description

@BangShou1st

Client or integration

Codex App

Area

Proxy / routing / tool-name normalization

Summary

A routed non-OpenAI model can emit the Codex view_image tool as default.view_image, which OpenCodex rejects as undeclared and aborts the stream:

stream disconnected before completion: routed provider emitted undeclared client tool "default.view_image"; only request-declared tools may be called

This still reproduces on OpenCodex 2.48.0.

The observed failure is similar to #3402 / #3403, but appears to be a different alias case.

#3403 fixed a declared namespaced tool whose provider echo used a different spelling:

default__apply_patch <-> default.apply_patch

This report is about a bare Codex tool being returned with an invented/default namespace:

view_image -> default.view_image

If the request declares bare view_image but does not declare default.view_image / default__view_image, the existing dotted ns.name alias support does not necessarily identify these as the same tool.

Current behaviour

  1. Codex exposes/uses the local view_image tool.
  2. A routed custom/non-OpenAI model or upstream compatibility layer returns the function call name as default.view_image.
  3. responses-undeclared-tool-guard treats it as a different, undeclared client tool.
  4. The SSE stream is aborted.
  5. Codex App shows reconnect attempts (reconnecting /5) and the turn fails.

Expected behaviour

OpenCodex should safely normalize a provider-added default. namespace back to the unique bare request-declared tool when that mapping is unambiguous.

For example, if the request-declared tool catalog contains exactly one bare tool named:

view_image

and does not contain a real namespaced tool whose canonical/alias name is default.view_image, then a provider echo of:

default.view_image

could be restored to:

view_image

before the undeclared-tool guard rejects it.

This should remain fail-closed when the mapping is ambiguous or the bare tool is not actually declared.

Suggested safety constraints

A conservative normalization could apply only when all of the following are true:

  1. The returned name has the form default.<name>.
  2. <name> is present as a request-declared bare tool.
  3. default.<name> / the corresponding namespaced identity is not itself a declared tool.
  4. The mapping resolves to exactly one declared tool identity.

Otherwise preserve the current undeclared-tool rejection.

This would avoid broadly stripping arbitrary namespaces while handling providers that serialize un-namespaced function calls under a synthetic default namespace.

Reproduction

  1. Run OpenCodex 2.48.0 with Codex App routed through a custom/non-OpenAI provider.
  2. Use a task that causes the model to inspect an image through Codex view_image.
  3. The routed provider returns default.view_image.
  4. Observe:
stream disconnected before completion: routed provider emitted undeclared client tool "default.view_image"; only request-declared tools may be called

Codex App then attempts to reconnect repeatedly.

Version

OpenCodex 2.48.0

Operating system

Windows

Provider and model

Custom / non-OpenAI routed model. The issue appears to be at the tool-call naming/compatibility boundary rather than in Codex's native OpenAI model path.

Related issues

Additional context

Prompt-level guidance in AGENTS.md was added to tell non-OpenAI models to use only request-declared tools and exact tool names, including explicitly avoiding invented prefixes such as default.. The failure still occurs, suggesting the default. prefix may be introduced by the model/provider tool-call serialization path rather than being reliably preventable through prompt instructions alone.

Checks

Activity

  1. added
    bugSomething isn't working
    streamingSSE, WebSocket, terminal stream frames
    on Sep 10, 2026
  2. lidge-jun commented on Sep 10, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 69 / 80

    이 이슈는 Codex App이 라우팅된 비-OpenAI 모델로 view_image를 쓸 때, 업스트림이 도구 이름을 default.view_image로 돌려보내면 OpenCodex가 선언되지 않은 클라이언트 도구로 보고 SSE를 끊는 버그입니다. 재현 메시지는 정확히 src/server/responses-undeclared-tool-guard.ts / src/bridge.ts가 쓰는 문장입니다. routed provider emitted undeclared client tool "…"; only request-declared tools may be called. 2.48.0에서도 난다고 했고, 지금 dev(c15a98caa, package 2.50.0)의 가드 로직을 보면 같은 거절이 그대로입니다.

    이미 비슷한 sor를 고친 적이 있습니다. #3403(머지됨)은 이미 네임스페이스로 선언된 도구가 ns__name과 ns.name 두 철자로 메아리칠 때 둘 다 같은 신원으로 받도록 src/responses/tool-name-aliases.ts와 undeclared-tool-guard를 고쳤습니다. #3402는 default.apply_patch ↔ default__apply_patch 쪽 관측이었습니다. 이번 보고는 그다음 구멍입니다. 요청 카탈로그에는 bare view_image만 있는데, 공급자/직렬화 계층이 합성 네임스페이스 default.를 붙여 돌려보냅니다. #3403 별칭은 “선언된 namespace+name”에서 출발하므로, bare만 있는 경우에는 default.view_image를 view_image로 되돌리지 않습니다.

    src/types/tools.ts normalizeDeclaredToolName도 code-mode exec 헬퍼 이름만 다루고, default. 접두를 벗기지 않습니다. 그래서 가드가 declared.has("default.view_image")에서 실패하고 스트림이 죽습니다. Codex App은 reconnect /5를 돌다 턴이 실패합니다. 프롬프트(AGENTS.md)로 “default. 붙이지 말라”고 적어도, 모델/프로바이더 직렬화가 붙이는 경우에는 막지 못한다는 작성자 관찰도 타당합니다.

    형제 PR #4171은 code-mode view_image를 unified exec로 넘기는 다른 축입니다. 이 이슈의 “bare ↔ default.bare 정규화”와 겹치지 않습니다. 중복 이슈로 닫지 말고, 가드/별칭 쪽 작은 패치로 받는 게 맞습니다. types/config 분할과도 무관합니다.

    라인 문제:

    src/server/responses-undeclared-tool-guard.ts undeclaredNameInItem - bare 호출 경로에서 normalizeDeclaredToolName 후 declared.has만 봅니다. default.view_image → view_image 폴백이 없어 스트림이 즉시 거절됩니다.

    src/types/tools.ts normalizeDeclaredToolName - code-mode helper만 정규화합니다. 합성 default. 네임스페이스는 범위 밖이라 이 버그를 막지 못합니다.

    src/responses/tool-name-aliases.ts / collectDeclaredWireToolNames - #3403은 선언된 namespace에서 dotted alias를 등록합니다. bare 선언만 있을 때 공급자가 붙인 default.를 읽어 되돌리는 경로는 아직 없습니다.

    src/bridge.ts (undeclared client tool 분기) - 가드가 이름을 거절하면 그대로 연결을 끊습니다. 재연결 루프의 직접 원인입니다.

    관련 #4171 - view_image 단어는 같지만 code-mode/exec 호환 이슈라서 이 티켓을 Fixes로 묶으면 안 됩니다.

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

    • 작성자가 제안한 조건(이름이 default.<bare>이고, bare가 선언되어 있고, default.<bare>/default__<bare>가 따로 선언되지 않았고, 매핑이 유일할 때만 허용)을 그대로 받을지
    • 정규화 위치를 undeclared-tool-guard로 둘지, normalizeDeclaredToolName/별칭 레이어로 올릴지
    • default 외 다른 합성 네임스페이스까지 열지, 아니면 default만 narrowly 허용할지
    • Windows Codex App 재현 fixture를 CI에 넣을지(없으면 단위 테스트로 wire name만 검증)

    너의 추천
    이슈를 열린 채로 두고, 위 네 조건을 지키는 작은 fail-closed 패치 PR을 받으세요. #3403을 넓히는 방향이 자연스럽고, #4171과는 분리하세요. 스트림을 끊는 실사용 버거라 우선순위는 중상입니다. 패치 PR이 오면 privacy/typecheck + default.view_image→view_image 단위 테스트 후 merge하면 됩니다.

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

  3. lidge-jun commented on Sep 11, 2026

    @lidge-jun
    Owner

    Fixed on dev by #4264.
    An invented default. namespace is normalized back to the declared bare tool when — and only when — the caller actually declared that bare name.
    Credit to @chilung-cgu, whose commits were carried unchanged into the release chain.
    Closing manually: pull requests here target dev, so GitHub's Closes linkage only fires on a merge into main. This will reach users in the next release.

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 workingstreamingSSE, WebSocket, terminal stream frames

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions