Repository navigation
fix(anthropic): let Claude Code auto mode's server review through the native OAuth lane - #8507
Conversation
… native OAuth lane
Claude Code in auto mode asks the Anthropic API to review risky tool calls
as part of each model request. A session without feature flags (telemetry
off, or behind a gateway) has no local classifier to fall back on, so on
the managed native Messages lane over Anthropic OAuth every reviewed call
was denied ("[server-classifier] ... server_no_result"). Three causes:
- The dangerous-tool-use-2026-09-03 beta and its top-level `safeguards`
body field were not admitted. They are now one nativeClientOnly
beta/body pair in beta-body-pairs.ts, the same policy as
context_management.
- OAuth renamed every declared client tool to `custom_<name>`, so the
server review matched none of the calls (`tool_uses: {}`). An observed
Claude Code client already sends the tool names its own OAuth login
uses, so it now keeps them; the reverse map is empty and the verdicts in
message_delta.delta.safeguard_results reach the client unchanged. Other
OAuth callers keep the prefix; the collision checks are unchanged.
- Background sessions (`claude --bg`) send `X-App: cli-bg`, but client
identity accepted only `cli`, so they fell back to the generic shape and
the OAuth API refused them with 400. `cli` and `cli-bg` are accepted.
Regression tests cover the pairing, the unprefixed names for an observed
client (and the prefix for a generic one), the end-to-end native OAuth
lane including relayed verdicts, and the cli-bg identity. The managed
native Messages contract in structure/data-planes/protocol-paths.md and
the protocol-paths guide are updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Caution Review failedThe pull request is closed. Walkthrough
Pre-merge checks:
|
|
✅ Deterministic PR hygiene checks passed. |
⏳ DRAFT
What to do
Review readiness checklist
3/4 boxes ticked. The PR is more than 10 commits behind Hygiene✅ Deterministic PR hygiene checks passed. |
… turns on Security review follow-up for lidge-jun#8507: recognition is header-based, so a client that copies Claude Code's identity also sends unprefixed tool names on the OAuth account, and an operator-added dangerous-tool-use beta forwards safeguards for unrecognized callers whose names stay prefixed. Co-authored-by: david-wang-0 <72378768+david-wang-0@users.noreply.github.com>
|
Maintainer judgment (bundle lane: Anthropic / Chat protocol fixes, head
|
Summary
Claude Code's auto mode fails through the managed native Messages lane on an Anthropic OAuth pool account: every action that auto mode sends for review is denied, and the Claude Code debug log says
[server-classifier] Bash: no server verdict for this call (server_no_result). Claude Code (2.1.322) asks the Anthropic API to review risky tool calls as part of each model request. A session that fetches no feature flags (telemetry or nonessential traffic turned off, or behind a gateway) has no local classifier to fall back on, so an action without a server verdict is denied rather than reviewed locally. On a direct connection the same request works.Three things on the native lane kept the review from happening. Each is fixed here, with a regression test.
anthropic-beta: …,dangerous-tool-use-2026-09-03,…and a top-levelsafeguardsfield (the classifier context), and reads the verdicts frommessage_delta.delta.safeguard_results. The beta was not on the native-client allowance, andsafeguardsis not a passthrough field, so both were removed and the API ran no review. Fix: one row insrc/adapters/anthropic/beta-body-pairs.ts,dangerous-tool-use-2026-09-03↔["safeguards"],nativeClientOnly: true, the same policy ascontext_management. The field is forwarded only while the beta survives into the final header, and only for an observed Claude Code client on first-party Anthropic.anthropicOAuthWireBodyprefixes every declared client tool withcustom_(Bash→custom_Bash). The server review matches calls by tool name, so with the beta and field forwarded it answered"safeguard_results":[{"type":"dangerous_tool_use","status":{"type":"available","tool_uses":{}}}]: it reviewed nothing, and Claude Code again found no verdict. An observed Claude Code client already sends the names its own OAuth login uses (Claude Code sendsBashover OAuth directly), sosrc/adapters/anthropic/passthrough.tsnow keeps an observed client's tool names (hasObservedAnthropicClientIdentity). The collision checks are unchanged. The reverse map is empty, sorestoreOAuthToolNamesInSse/…InMessagerelay the answer untouched, includingsafeguard_results. Other OAuth callers (generated Responses traffic, unobserved clients) keep the prefix. With both changes the same request answered"tool_uses":{"toolu_…":{"type":"evaluated","outcome":"not_flagged"}}and Claude Code ran the command.claude --bgsessions sendX-App: cli-bg, butsrc/adapters/anthropic/client-identity.tsaccepted onlyX-App: cli, a required header. So every background session fell back to the generic shape: its native betas were dropped, its tools prefixed, and the first-party OAuth API refused the request with 400 ("The provider rejected the request."). It now acceptscliandcli-bg. Any other value still makes the client unobserved. No other place insrc/comparesX-App; the proxy's own fingerprint keeps sendingcli.Evidence comes from a live Claude Code 2.1.322 session in auto mode through a local proxy on an Anthropic OAuth pool account, with a logging relay between client and proxy. The captured request carried the beta and
safeguards. The verdict frames are quoted above, before and after the tool-name fix. A curl through the patched lane without the observed identity headers still drops both, as intended.With the three changes applied as a local patch on OpenCodex 2.100.1 (these files are identical on
dev), auto-modeclaude -pruns asking forcurl -sI https://example.com | head -1completed with no[server-classifier]warning for Opus, Sonnet and Fable models on Anthropic OAuth pool accounts, both on direct model routes and through a combo targeting the pool account. A background (claude --bg) session and a resumed background session (--bg --resume) each ran Bash and SendMessage calls in auto mode.Precondition and follow-up: this fixes the native lane (
protocols.rollout.managedMessagesNativeandmanagedMessagesNativeOAuthon). The legacy bridge (messages → responses-internal → ir → messages, the default for OAuth providers) drops both the beta andsafeguardsas well, so auto mode through the bridge on an OAuth account still has no server review. That is left as a follow-up rather than mixed into this change. Combo routes on/v1/messagesalready take the native lane oncemanagedMessagesNativeis on (nativeComboSourceinsrc/server/claude-messages.ts), so they get the fix too; the combo case above was verified live.Security boundary: change 2 alters the body sent with an OAuth token (tool names are no longer rewritten for an observed Claude Code client), and change 3 widens which caller headers count as an observed Claude Code client. Both touch the authentication surface, so per
MAINTAINERS.mdthis needs a maintainer to sponsor it (maintainer-sponsored). Neither grants credential authority: the identity bundle stays a compatibility observation, and no caller credential, beta outside the allowlist, or field outside the pairing table reaches the provider. The new pairing row follows the existingcontext_managementrule exactly.Docs:
structure/data-planes/protocol-paths.md(managed native Messages contract: pairing table, OAuth tool names,X-Appvalues; still 600 lines) anddocs-site/src/content/docs/guides/protocol-paths.md(English source). The Turkish translation does not cover these sentences and is not contradicted.Verification
Bun 1.4.4 (the version
scripts/test.tsexpects), on Linux. Branch based ondevatddee2c200.bun test tests/adapters/anthropic/anthropic-messages-passthrough-oauth.test.ts tests/adapters/anthropic/anthropic-beta-body-pairs.test.ts tests/adapters/anthropic/anthropic-beta-allowlist.test.ts tests/adapters/anthropic/anthropic-messages-passthrough.test.ts tests/adapters/anthropic/anthropic-client-identity.test.ts tests/claude-integration/messages-native-oauth.test.ts tests/claude-integration/messages-native.test.ts tests/claude-integration/messages-native-scope.test.ts: 303 pass, 0 fail (2407expect()calls, 8 files).src/changes stashed, the new and updated cases fail (6 in the beta, OAuth-body and OAuth-lane files; 1 inanthropic-client-identity.test.ts). With the changes they pass.bun run test:changed: 6850 pass, 34 skip, 0 fail (6884 tests across 280 files), on the final source and tests before two one-line doc edits.OCX_TEST_MAIN_TIMEOUT_MS=3600000 bun run test --parallel=2: parallel lane 63131 pass, 690 skip, 6 fail (63827 tests across 3019 files, 1627 s); serial lane 21 files, 748 pass, 24 skip, 0 fail. None of the 6 failures is in a file this change touches or imports. Run alone,tests/providers/deepseek-image-files.test.ts(1) andtests/usage/request-decompress.test.ts(3) pass; in the suite they hitRangeError: Out of memoryunder the per-process address-space cap the run used.tests/service/service-definition-escaping.test.ts(launchd plist parse through a localplutil) andtests/codex-integration/codex-composed-acceptance.test.ts("A-reduced", OFF Codex home) fail the same way on the base commitddee2c200without this change. The run used two workers instead of four and a longer main-lane timeout because of a per-process memory limit on the test machine; with two workers the default 900 s budget expired.bun run typecheck: no type errors in 2,384 files.bun run structure:check: structure/ SSOT checks passed.bun run privacy:scan: passed.New regression cases:
anthropic-beta-body-pairs.test.ts, "auto mode server review": the beta andsafeguardsare forwarded for an observed client (key and OAuth); a generic caller gets neither, and the drop is named;safeguardswithout the beta in the final header is dropped; a compatible destination receives neither.anthropic-messages-passthrough-oauth.test.tsandanthropic-beta-allowlist.test.ts: an observed client's tool names, history andcontext_management.exclude_toolsare forwarded as sent, with an empty reverse map. A generic OAuth caller (including one whose operator header admitscontext_management) keeps thecustom_prefix and theexclude_toolsrewrite.messages-native-oauth.test.ts, end to end throughhandleClaudeMessages: an observed client in auto mode sends the beta,safeguardsand unprefixed names, and the client stream carriessafeguard_resultsunchanged. The same request without the observed identity is prefixed and loses the review.anthropic-client-identity.test.ts: anX-App: cli-bgbundle is observed and forwarded with its ownX-App;cli-fg,bg,CLI-BGandcli,cli-bgare not.Checklist
Agent attribution: Claude Opus 5.5 (Claude Code).
🤖 Generated with Claude Code
Review readiness checklist
This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:
Required local validation passed; commands, results, and any full-suite exception are documented.
I pushed my PR to a recent dev commit (at most 10 behind; a maintainer may still ask for the exact tip before merge).
I resolved all correct Codex and CodeRabbit findings.
My PR is ready for review.
Maintainer follow-up (bundle lane, head 4c90221)
Commits added on this branch by a maintainer (no force push): a merge of current
devand a docs note from the security review.custom_prefix arrived with Anthropic OAuth (14fc3fc, "required or Claude Pro/Max OAuth 401s" for third-party tool names). f34f742 then renamed it fromproxy_while hardening client fingerprints. A recognized Claude Code client already sends the names its own OAuth login sends, so keeping them reproduces the first-party request. Generated Responses traffic and unrecognized callers keep the prefix.safeguardsand identity headers reach onlyapi.anthropic.comfor a recognized client.docs-site/.../guides/protocol-paths.mdnow states that, and that an operator-addeddangerous-tool-usebeta forwardssafeguardsfor unrecognized callers whose names stay prefixed.Verification at 4c90221:
origin/devwith only the tests applied. The branch passes 164/164.bun run typecheck,structure:checkandprivacy:scanpass, as do the file-size ratchet, test layout and Lab boundary.bun test --changed=<merge-base>: 285 files, 8 fail / 8 errors. The failures are incatalog-omit-disabled-native(fails identically on unmodifieddevon this host) andchat-native-spend(22/22 alone, a load flake).Summary by CodeRabbit