Skip to content

fix(responses): restore code-mode goal helpers - #5659

Closed
Ingwannu wants to merge 1 commit into
devfrom
ingw/fix-code-mode-goal-helpers-5495
Closed

Ingwannu wants to merge 1 commit into
devfrom
ingw/fix-code-mode-goal-helpers-5495

Conversation

@Ingwannu

Copy link
Copy Markdown
Owner

Summary

  • Restore routed create_goal, get_goal, and update_goal calls as nested helpers of a genuinely declared code-mode exec tool.
  • Accept both bare and provider-invented default. spellings, then compile them to the matching fixed tools.<helper>(...) call instead of falling through to tools.exec_command(...).
  • Preserve the authorization boundary: a genuinely declared bare goal tool keeps its identity, and catalogs declaring neither the bare tool nor exec still fail closed.
  • Add focused regression coverage for normalization, compilation, full JSON restoration, and undeclared-tool rejection.

Closes #5495.

Verification

  • bun test tests/responses/responses-code-mode-goal-helpers.test.ts tests/responses/responses-code-mode-shell-compile.test.ts tests/responses/responses-code-mode-patch-compile.test.ts: 18 passed, 0 failed.
  • bun run typecheck: passed.
  • bun run structure:check: passed.
  • git diff --check: passed.
  • Every command used isolated temporary HOME, CODEX_HOME, and OPENCODEX_HOME paths.
  • The full repository suite has not yet been run on this head, so this PR starts as draft and must not merge until exact-head CI and maintainer review complete.

@lidge-jun Please review the bounded tool-admission decision. The patch widens only the fixed nested-helper vocabulary advertised by Codex code mode; it does not accept arbitrary provider tool names.

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.

@coderabbitai

coderabbitai Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

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.

@lidge-jun

Copy link
Copy Markdown
Owner

리뷰 · 우선순위 61 / 80

이 PR은 Codex 코드 모드에서 스트림이 끊기는 버그를 고칩니다. 기준 브랜치는 dev이고, #5495를 닫습니다.

코드 모드에서 create_goal, get_goal, update_goal은 맨 위 도구가 아닙니다. exec 안에서 tools.함수이름(...)으로만 호출됩니다. 카탈로그에는 exec 옆에 wait 같은 도구가 있을 수 있습니다. 라우팅된 모델이 호출 이름 칸에 default.update_goal을 넣으면, 그 이름이 허용 목록에 없어서 스트림이 끊깁니다. 오류는 routed provider emitted undeclared client tool "default.update_goal"입니다.

이 PR은 그 세 이름을 코드 모드 도우미 목록에 넣습니다. 카탈로그에 exec가 있고 그 목표 도구를 따로 선언하지 않았으면, update_goal과 default.update_goal은 exec로 바뀝니다. 본문은 const result = await tools.update_goal(...); text(result);로 컴파일됩니다. 그 도구를 맨 위에 선언했으면 그 이름이 남습니다. 아무것도 선언하지 않았으면 그대로 거절합니다. src/types.ts는 src/types/tools.ts를 다시 내보내므로, 수정은 나뉜 파일에 있습니다.

src/types/tools.ts의 NAMESPACED_BARE_ALIAS_EXCLUDED_NAMES 주석 - 금지 이름이 여섯 개라고 적혀 있습니다. 이번 변경 전에는 exec와 도우미 다섯 개로 여섯이 맞았습니다. 지금은 목표 이름 세 개가 더해져 아홉 개입니다. 이 주석은 이름만 보고 권한을 정하는 설명입니다. 여섯으로 남아 있으면 새로 넣은 세 이름이 그 목록에 있다는 사실이 안 보입니다.

normalizeDeclaredToolName 주석 - exec_command, shell_command, apply_patch, view_image만 적혀 있습니다. write_stdin은 원래 빠져 있었고, 이번 세 이름도 없습니다.

tests/responses/responses-code-mode-goal-helpers.test.ts - 끊긴 스트림의 조건은 카탈로그가 {exec}일 때, 복원 전 항목 이름 default.update_goal을 가드가 통과시키는가입니다. 테스트는 이름을 exec로 바꾸는 함수, 이미 복원된 항목의 가드, 빈 카탈로그에서의 거절을 봅니다. {exec} 카탈로그에 원본 항목을 넣어 undeclaredToolCallNameInResponse가 비는지 보는 줄은 없습니다.

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

이 세 이름은 NAMESPACED_BARE_ALIAS_EXCLUDED_NAMES에도 들어갑니다. 네임스페이스 도구의 안쪽 이름이 update_goal이면 접두사 없는 별칭을 만들지 않습니다. view_image와 같은 규칙입니다. MCP 도구가 정말로 update_goal이면, 모델이 짧은 이름만 되풀이할 때 그 도구로 돌아가지 않습니다.

이슈는 namespace가 "default"이고 name이 "update_goal"인 형태는 이번 로그에서 보지 못했다고 적습니다. routedCustomToolTargetName은 namespace가 문자열이면 도우미 정규화 전에 포기합니다. 이번 패치는 이름 칸에 default.update_goal이 통째로 들어온 형태를 고칩니다.

PR은 초안입니다. Cross-platform CI 실행 35829386877의 결론은 cancelled입니다. 작성자도 이 커밋에서 전체 스위트는 아직이라고 적었습니다.

너의 추천

방향은 맞습니다. 초안인 채로 두고, 이 커밋의 CI가 끝난 뒤에 머지 후보로 올리세요.

가드 테스트에 한 줄을 더하세요. 카탈로그 {exec}와 원본 default.update_goal을 undeclaredToolCallNameInResponse에 넣고, 결과가 비는지 보면 이슈의 오류와 같은 지점을 잠급니다.

주석의 "여섯"은 "아홉"으로 고치세요. normalizeDeclaredToolName 설명에는 세 목표 이름을 넣으세요.

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

lidge-jun added a commit that referenced this pull request Sep 23, 2026
Carries #5659. Routed create_goal, get_goal and update_goal calls (bare or
with a provider-invented default. prefix) are accepted as nested helpers of a
genuinely declared code-mode exec and compiled to the matching
tools.<helper>(...) call instead of falling through to exec_command. A
genuinely declared bare goal tool keeps its identity, and a catalog that
declares neither the tool nor exec still fails closed.

Folded review fixes: the guard is asserted on the original unrestored wire
name, an unlisted helper-like name is proven not admitted, bare-goal
precedence is covered through full restoration, the authorization comments in
src/types/tools.ts name the goal helpers, and the codex integration guide
describes the repair.

Closes #5495. Supersedes #5659.

Co-authored-by: ingwannu <186453546+Ingwannu@users.noreply.github.com>
lidge-jun added a commit that referenced this pull request Sep 23, 2026
…ement, goal helpers, Devin retry delays (#5675)

* docs(devlog): plan bundle lane E (responses and combo)

* fix(devin): accept generated approximate retry delays

Carries #5629. The shared retry-delay parser accepts the generated
"retry after ~180s" approximation marker after Retry-After, and the bounded
Devin replay re-evaluates the delay on every attempt within the existing
cumulative ceilings.

Folded review fixes: a repeated approximation marker ("~1 minute ~30
seconds") now rejects the hint instead of silently shortening it to the first
component, and the cloud-direct comment no longer claims the marker blocks
re-parsing.

Supersedes #5629.

Co-authored-by: Epinephrine <27862058+luvs01@users.noreply.github.com>

* fix(responses): restore code-mode goal helpers

Carries #5659. Routed create_goal, get_goal and update_goal calls (bare or
with a provider-invented default. prefix) are accepted as nested helpers of a
genuinely declared code-mode exec and compiled to the matching
tools.<helper>(...) call instead of falling through to exec_command. A
genuinely declared bare goal tool keeps its identity, and a catalog that
declares neither the tool nor exec still fails closed.

Folded review fixes: the guard is asserted on the original unrestored wire
name, an unlisted helper-like name is proven not admitted, bare-goal
precedence is covered through full restoration, the authorization comments in
src/types/tools.ts name the goal helpers, and the codex integration guide
describes the repair.

Closes #5495. Supersedes #5659.

Co-authored-by: ingwannu <186453546+Ingwannu@users.noreply.github.com>

* fix(responses): stop combo failover once a request has spent its ambiguous replacement

Carries #5646. Once a request has spent its retryOnReset replacement, the
first send may already have run the turn, so a replacement that answers 200
and then fails with zero output must not be sent again. RequestExecutionBudget
now reports ambiguousResendSpent from the one shared grant; combo failover
stops when it is spent and settles the answer with the shared
settleOperatorReplacement rule (a resendable status becomes the replay
refusal, anything else keeps its status with the non-replayable marker). On
the direct path the streamed opaque-blob rebuild is skipped once the grant is
spent. Carried before #5633 so its WebSocket replacement row is never exposed
to the third-send gap.

Supersedes #5646.

Co-authored-by: Fred Amartey <43480311+FredAmartey@users.noreply.github.com>

* fix(responses): let retryOnReset replace a Codex WebSocket send that died unanswered

Carries #5633. A Codex WebSocket that opens and then closes or errors under
its create frame before any Responses event is the same unknown state as an
HTTP connection that resets before its head. For a provider that opted into
retryOnReset, the request-resend gate may now spend the request's single
replacement on it (one replacement per logical request, self-contained body
only). The exchange records the stage it reached; silence keeps its 504 and a
drop after a relayed event keeps its errored 200. Providers that have not
opted in are unchanged.

Carried after #5646, so the WebSocket replacement row inherits the spent-grant
stop. Folded review fixes: the duplicate settleOperatorReplacement import the
pair merge produced is removed, and responses-failover.md states the 2xx
replacement contract once as settled for all three replacement rows.

Supersedes #5633.

Co-authored-by: Fred Amartey <43480311+FredAmartey@users.noreply.github.com>

* fix(combo): fail over undeclared zero-output tools

Carries #5489 (net diff; its upstream/dev merge commit is dropped). When a
runTurn adapter's first meaningful event in a combo attempt is a tool call the
current request did not declare, the existing fail-closed refusal is projected
as a pre-commit 502 so the combo can hop to the next target with the same tool
catalog. Chat Completions and Anthropic Messages inbound requests keep their
existing behaviour.

Folded review fix: the non-streaming path now applies the same boundary as the
streaming preflight. An undeclared tool call after a replay-unsafe heartbeat
(an adapter-side effect already ran) keeps the refusal on that child instead of
sending the turn to another target. New streaming and non-streaming cases prove
exactly one dispatch; the non-streaming one fails without the gate. The combos
guide gains the hop row in every locale and responses-failover.md records the
runTurn boundary.

Related to #5407 (covers its Responses path only; the reported Claude Code
Anthropic Messages path is unchanged). Supersedes #5489.

Co-authored-by: Yu Zhang <34849476+AaronZ345@users.noreply.github.com>

* fix(combo): keep failures after a replay-unsafe side effect on their child

Found by the lane's adversarial review of the carried #5646/#5489 changes.

- A runTurn adapter that emits a replay-unsafe heartbeat (it already ran a
  local side effect, as Cursor does) and then errors or ends empty before any
  output returned a plain 502, so a combo sent the turn to the next target and
  could repeat the side effect. Streaming and non-streaming paths now mark that
  502 non-replayable, and the combo stops on the child. This predates the
  carried commits; it sits on the same boundary structure/runtime.md states.
- A scope derived from a shape-compatible budget that implements
  claimAmbiguousResend but not ambiguousResendSpent reported "not spent" after
  it claimed the grant, which would let a combo hop on a zero-output 200 from
  the replacement (a third send). Grants claimed through the bridge are now
  latched per bridged parent and visible to every sibling scope.

Both are covered by new tests that fail without the fix.

* fix(responses): let a WebSocket replacement that resets use a second grant

Review finding on #5675. With retryOnReset.replacements set to 2, a dead
Codex WebSocket spends the first grant on its HTTP replacement; if that
replacement resets before its head, the WebSocket row settled it as the
replay refusal at once, so the configured second replacement was never
reachable. The reset is the pre-header row again, so the row now asks the
same gate (and the send budget) once more and resends only when a grant
remains; with the default of one it still settles as the refusal. The loop is
bounded by the request's finite allowance.

---------

Co-authored-by: Epinephrine <27862058+luvs01@users.noreply.github.com>
Co-authored-by: ingwannu <186453546+Ingwannu@users.noreply.github.com>
Co-authored-by: Fred Amartey <43480311+FredAmartey@users.noreply.github.com>
Co-authored-by: Yu Zhang <34849476+AaronZ345@users.noreply.github.com>
@lidge-jun

Copy link
Copy Markdown
Owner

Carried onto dev in bundle PR #5675 (squash-merged as aed3bb8), rebuilt on current dev as commit 279b8ff on the lane branch with a Co-authored-by trailer for you, so the credit stays on the merged commit. Closing this one as superseded. Thank you for the fix.

@lidge-jun lidge-jun closed this Sep 23, 2026
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