Skip to content

fix(responses): relay undeclared passthrough tool calls on chat and anthropic inbound - #6732

Merged
lidge-jun merged 4 commits into
devfrom
codex/6648-passthrough-relay-undeclared-anthropic-chat
Oct 8, 2026
Merged

lidge-jun merged 4 commits into
devfrom
codex/6648-passthrough-relay-undeclared-anthropic-chat

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Summary

Closes #6648.

Claude Code (and other /v1/messages or /v1/chat/completions clients) routed to a provider on the Responses passthrough path had their turn killed with routed provider emitted undeclared client tool "Write"; only request-declared tools may be called whenever the model called a tool the request had not declared, for example a deferred or runtime-discovered tool.

The documented contract (#4799, structure/adapters/compatibility-contracts.md "Undeclared-tool refusal is an inbound-protocol claim") is that only the responses inbound wire refuses an undeclared call (#1700); chat and anthropic relay it to the client's own tool runner. run-turn-execution.ts and adapter-delivery.ts already scope it that way. The passthrough path did not: passthrough-delivery.ts refused on every wire.

This change scopes only the two passthrough refusal points (the SSE block rewrite and the buffered JSON 502) by inboundWire. undeclaredToolGuardActive is kept on every wire because it also gates namespace normalization and continuation suppression, so:

  • responses inbound refusal is unchanged (stream, buffered JSON, reframed SSE).
  • A relayed undeclared call on chat/anthropic is still never written to previous_response_id replay state.
  • Declared-name normalization (for example default.Read → Read) still runs on every wire.
  • The issue's optional Claude builtin-name allowlist and case-insensitive fallback are intentionally not included; they would authorize undeclared names on the responses wire.

inboundWire is set server-side by each endpoint handler, so a /v1/responses caller cannot opt out of #1700 enforcement.

Verification

  • Reproduction first: the new endpoint test tests/responses/passthrough-deferred-tools.test.ts failed on unmodified dev (22 pass / 8 fail; every Anthropic and Chat stream/buffered case refused Write with the message above) and passes after the fix (30 pass / 0 fail). It drives /v1/messages and /v1/chat/completions through a Responses-adapter passthrough provider, covers SSE and JSON upstreams, undeclared and declared names, namespace normalization, and asserts /v1/responses still refuses.
  • bun test tests/responses/passthrough-deferred-tools.test.ts tests/responses/responses-undeclared-tool-guard.test.ts tests/responses/chat-completions-deferred-tools.test.ts — 133 pass / 0 fail (independent reviewer run).
  • Also run: tests/adapters/bridge.test.ts, layout and file-size ratchet tests (27 pass), bun run typecheck, bun run structure:check, bun run privacy:scan.
  • Independent review of exact head a4889429d3: approved, no blocking findings. Probes confirmed byte-identical responses refusal output against the parent commit, no wire spoofing through body or headers, and replay suppression on both SSE and JSON paths.
  • Full suite not run locally to limit contention across concurrent worktrees; coverage beyond the focused files is left to CI.

Checklist

  • Targets dev
  • Focused regression test added and registered in both layout inventories
  • structure/transports/responses-wire-shapes.md updated for the owned source change
  • No GUI changes

Existing framing test updated

tests/responses/chat-sse-framing-guard.test.ts (from the #6461 carry, 2753ef9) protects SSE framing alignment: the passthrough guard must read the same CR/LF/CRLF event boundaries as the client projection, so framing cannot hide a call from validation. It used chat-inbound refusal as its observable, which this PR intentionally removes. The refusal-under-CR-framing cases now run against /v1/responses, where refusal still applies, with the same stream/fragmented/live-before-EOF fixtures. The chat cases now assert the same CR-framed call is relayed intact as tool_calls. A mutation check by the reviewer confirmed the coverage holds: making only the guard blind to CR delimiters fails all five Responses refusal cases.

The branch also merges dev and joins one layout.json registration onto an existing line, because dev at 1a8377d (#6721) had reached 2,000 lines and tripped the file-size ratchet (NEW_OVERSIZED) for every PR.

Additional verification

  • bun test $(rg -l 'undeclared client tool|undeclaredTool|enforceDeclaredToolNames' tests) plus sse-rewrite-line-endings, layout and ratchet tests: 604 pass / 0 fail across 23 files (independent reviewer run on c392328689).
  • Independent review of final head c3923286897b8b96d5e6c283bdc7f441129723bf: approved, no blockers.

@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner October 8, 2026 01:28
@lidge-jun lidge-jun added the bug Something isn't working label Oct 8, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 8, 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-10-08T01:30:08.352452Z a488942 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.

@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: b154d852-b447-4457-bde8-3c3bd234529c
📥 Commits

Reviewing files that changed from the base of the PR and between e18ee22 and c392328.

📒 Files selected for processing (1)
  • tests/responses/chat-sse-framing-guard.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

Passthrough delivery now limits undeclared tool-name refusal to Responses inbound requests. The SSE guard accepts an enforcement flag, and integration tests cover Anthropic, Chat Completions, and Responses requests across streaming and upstream response formats.

Changes

Passthrough guard behavior

Layer / File(s) Summary
Configurable tool-name guard
src/server/responses-undeclared-tool-guard.ts
The SSE undeclared-tool guard accepts an enforcement flag that defaults to true. When enforcement is disabled, it skips undeclared-call detection and retains payload normalization.
Inbound-wire policy in passthrough delivery
src/server/responses/passthrough-delivery.ts, structure/transports/responses-wire-shapes.md
Passthrough delivery disables declared tool-name enforcement for chat and anthropic inbound wires. Responses inbound requests retain undeclared-tool refusal for SSE and JSON. The documentation describes the wire-specific policy.
Passthrough and framing coverage
tests/responses/passthrough-deferred-tools.test.ts, tests/responses/chat-sse-framing-guard.test.ts, tests/fixtures/test-layout-expected.json, scripts/test-layout/layout.json
The tests cover tool calls across inbound wires and response formats. They also check that Chat framing cases relay calls while Responses framing cases reject undeclared calls. Test-layout files include the new test mapping.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to c3923

No merge-blocking issue is identified in the reviewed changes; normal checks can proceed.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 4 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR satisfies the coding requirement in #6648. src/server/responses/passthrough-delivery.ts derives enforceDeclaredToolNames from inboundWire and passes it to `createUndeclaredToolCallGuardBl…
Out of Scope Changes check ✅ Passed The changes remain within #6648. The delivery change implements inbound-wire-specific refusal. The guard parameter preserves normalization and replay-suppression behavior required by the compatibility…
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: undeclared passthrough tool calls are relayed for Chat and Anthropic inbound wires in the Responses passthrough path.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@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: a4889429d3

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

const { commitReasoningReplayServingRoute, recordTerminalOutcomes } = responseEffects;
const { parsed, route, subagentQuotaFailureModel, clientRequestedStream, translatorBudget } = requestState;
const { parsed, route, subagentQuotaFailureModel, clientRequestedStream, translatorBudget, inboundWire } = requestState;
const enforceDeclaredToolNames = inboundWire !== "chat" && inboundWire !== "anthropic";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update the chat framing tests for relay semantics

When a Responses upstream emits an undeclared tool for a Chat inbound request, this line now disables the refusal, but the five cases in tests/responses/chat-sse-framing-guard.test.ts:93-121 still require an undeclared client tool error and forbid tool_calls. Those tests exercise this exact passthrough path with both streaming modes and CR-fragmented SSE, so the changed-source test suite will fail consistently; update them to assert successful relay while retaining their framing coverage.

AGENTS.md reference: src/AGENTS.md:L24-L26

Useful? React with 👍 / 👎.

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@lidge-jun
lidge-jun merged commit b1e1735 into dev Oct 8, 2026
33 of 36 checks passed
@lidge-jun
lidge-jun deleted the codex/6648-passthrough-relay-undeclared-anthropic-chat branch October 8, 2026 03:34
geunwoojun99 added a commit to geunwoojun99/opencodex that referenced this pull request Oct 8, 2026
Merge b1e1735 additively after the specified dev c0f8165. The four newer upstream commits are 18a8cda (lidge-jun#6731), 13348ce (lidge-jun#6729), 8d30945 (lidge-jun#6713) and b1e1735 (lidge-jun#6732); their history is preserved unchanged.

The only content conflict is src/combos/jev.ts, where lidge-jun#6731 edited the endpoint resolver that the P0 exchange extraction moved to src/combos/jev-service-exchange.ts. This merge keeps the P0 side of jev.ts byte for byte. The exchange resolver still applies the pre-lidge-jun#6731 trailing-slash normalization after this commit; carrying the shared endpoint authority into it is a separate follow-up commit.

Co-authored-by: SeongwoongCho <35558061+SeongwoongCho@users.noreply.github.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
@lidge-jun lidge-jun mentioned this pull request Oct 8, 2026
3 tasks done
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