Skip to content

[bug] Devin drops namespaced client tool identity and rejects CUA js #4456

Closed

Description

@jeongjin0

Client or integration

Codex App

Provider or upstream service

Devin / Cognition cloud-direct (devin, model swe-2)

OpenCodex version

@bitkyc08/opencodex 2.52.0 and dev at dc33113a9

Endpoint or capability

/v1/responses streaming client tool calls with namespaced MCP tools

Current behaviour

The Devin adapter advertises every tool to Cognition as the local tool.name, dropping its Codex namespace. For a request that declares CUA as mcp__cua_repl__js, Cognition sees and returns js. runTurn forwards that bare name unchanged, while the Responses bridge authorizes the canonical request name. The fail-closed guard then terminates the turn with HTTP 502.

Redacted local observations on 2026-09-13 recorded 14 identical 502s across four conversations from devin/swe-2 between 12:28 and 12:33 KST.

Expected behaviour

When Devin returns a bare tool name that uniquely identifies one request tool, the adapter should restore the request's canonical wire identity before the shared Responses bridge validates and dispatches it. A bare name shared by multiple request tools must fail without choosing by declaration order. Unknown names must remain subject to the existing undeclared-tool guard.

Minimal redacted request or reproduction

# OpenCodex 2.52.0, Codex CLI/App 0.154.0
# Route a Codex task through devin/swe-2 with the CUA MCP tool declared as:
#   namespace: mcp__cua_repl
#   name: js
# Ask the model to continue a browser/UI task that requires that tool.
# The adapter sends Cognition the bare name "js" and returns the same bare name.

Actual response or error

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

Upstream documentation

Cognition's cloud-direct Connect-RPC endpoint has no public tool-naming specification. The concrete client contract is the Responses request catalog: the client declared mcp__cua_repl__js, and OpenCodex must preserve that tool identity across an adapter that intentionally advertises its local name upstream.

Relevant source on dev: src/adapters/devin.ts maps tools with name: tool.name and emits event.name unchanged. The shared bridge uses namespacedToolName(namespace, name) as the authorized wire identity.

Suggested mapping or implementation notes

Build a request-scoped map from each advertised local name to namespacedToolName(tool.namespace, tool.name). Restore only uniquely owned names on tool_call_start. Represent collisions explicitly and emit a non-retryable adapter error before dispatch; preserve unknown names for the shared fail-closed guard.

Keep Cognition's current bare-name request contract and catalog nudge unchanged. This makes the response half symmetric without broadly authorizing bare aliases in the provider-independent bridge.

Additional context and attachments

Checks

  • I searched existing provider and compatibility issues.
  • The request and response were redacted.
  • The expected behaviour is based on an upstream specification or a concrete client requirement.

Activity

  1. added
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    toolstool_calls, MCP, web-search / sidecar tools
    on Sep 13, 2026
  2. lidge-jun commented on Sep 13, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 76 / 80

    설명

    이 이슈는 Devin cloud-direct가 요청에 선언된 이름공간 도구 이름을 버리고, Cognition이 돌려준 짧은 이름만 Responses 다리로 넘기면서 턴이 502로 죽는 버그입니다. 보고는 Codex App + devin/swe-2에서 CUA를 mcp__cua_repl__js로 선언했을 때, 어댑터가 Cognition에는 js만 알리고, 응답도 js로 내려온 뒤 공유 undeclared-tool 가드가 routed provider emitted undeclared client tool "js"로 끊는 패턴입니다. 2026-09-13 12:28–12:33 KST에 같은 502가 네 대화에서 14번 찍혔다고 하니, 추측이 아니라 재현된 운영 장애입니다.

    지금 dev(HEAD 261bab915)의 src/adapters/devin.ts를 보면 요청 절반은 의도적으로 짧은 이름입니다. mapOcxToolsToDevin이 name: tool.name만 넣고 namespace를 붙이지 않습니다. #4285에서 그렇게 바꾼 뒤 #4415가 같은 bare-name 계약을 문서/너지로 고정했습니다. 그런데 응답 절반인 tool_call_start는 emit({ type: "tool_call_start", id: event.id, name: event.name })로 받은 이름을 그대로 보냅니다. 공유 Responses 다리는 namespacedToolName(namespace, name)으로 권한을 검사하므로, 요청 카탈로그의 mcp__cua_repl__js인데 어댑터가 js를 내면 fail-closed가 맞습니다. 즉 “요청은 bare로 광고하고, 응답은 canonical로 복원하지 않음”이 구멍입니다.

    이 타이밍이 중요합니다. 바로 전에 #4453으로 Devin prompt cache 요청 + 세션 identity가 dev에 들어왔고, #4450으로 헤더 마감도 고쳤습니다. Devin 줄은 “긴 턴을 살리고 캐시를 쓰게” 쪽으로 가고 있는데, 도구 한 번만 호출해도 턴이 502면 그 이득이 사라집니다. 고침 모양도 이슈가 잘 짚었습니다. 요청 범위 맵 advertisedLocalName → namespacedToolName(...)을 만들고, tool_call_start에서 유일하게 소유된 이름만 복원하고, 충돌은 선언 순서로 고르지 말고 어댑터 비재시도 오류로 끊고, 모르는 이름은 기존 undeclared 가드에 맡기세요. Cognition 쪽 bare 요청 계약과 카탈로그 너지는 유지하는 편이 #4415와 맞습니다.

    경로 src/adapters/devin.ts mapOcxToolsToDevin - 요청 광고는 계속 tool.name만 써도 됩니다. 여기에 namespace를 다시 붙이면 Cognition 계약이 깨집니다. 고칠 곳은 응답 복원입니다.

    경로 src/adapters/devin.ts tool_call_start emit - event.name을 그대로 내보내면 안 됩니다. 같은 턴의 요청 도구 목록으로 만든 identity 맵에서 canonical wire name을 찾아 넣으세요. 충돌(여러 도구가 같은 local name)은 첫 선언을 고르지 말고 명시적 오류로 막으세요.

    경로 공유 Responses undeclared-tool 가드 - 여기서 bare alias를 넓게 허용하는 우회는 하지 마세요. Devin만의 대칭 복원이 맞고, 다른 어댑터까지 bare를 열어 주면 #4176/exec nested-helper 가드와 충돌합니다.

    이슈 본문 재현 절차 - 셸 예시는 개념 재현이라 CI fixture로 바로 쓰기 어렵습니다. mapOcxToolsToDevin + stream tool_call_start를 묶은 단위 테스트(유일 매핑 성공 / 충돌 실패 / 미지 이름 통과→가드)를 PR에 넣는 게 검증 기준이 됩니다.

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

    • 복원 맵을 createDevinAdapter 클로저에 둘지, cloud-direct 쪽과 공유 헬퍼로 뺄지
    • local name 충돌 시 HTTP/에러 코드(비재시도 adapter error) 문구를 어떻게 고정할지
    • #4415 문서/너지에 “응답은 canonical로 되돌린다” 문장을 같이 고칠지
    • 2.52.0 핫픽스 후보인지, dev(2.53.0 라인)만으로 충분한지

    너의 추천
    우선순위 높게 help wanted로 두고, 첫 PR은 Devin 응답 identity 복원 + 충돌/미지 이름 테스트만으로 좁히세요. 공유 bridge에 bare alias를 넓히는 PR은 거절하세요. types/config 분할과 무관하고, #4453 캐시 작업과도 파일 충돌이 거의 없으니 병렬로 가져갈 수 있습니다.

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

  3. lidge-jun commented on Sep 13, 2026

    @lidge-jun
    Owner

    Fixed on dev via #4457, carried and merged inside lane C's tip #4487 (merge commit 55bb9f3, verified as an ancestor of origin/dev). The landed commits credit @jeongjin0 with a Co-authored-by trailer, and the carry also folded a fail-closed guard for a canonical/local tool-name collision.

    Cross-platform CI run 34744712476 concluded success on the exact merged head. Closing manually because this merges into dev rather than the default branch.

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

    providerProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reportstoolstool_calls, MCP, web-search / sidecar tools

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions