Skip to content

fix: add user:sessions:claude_code scope to anthropic_oauth_subscription#576

Open
divybot wants to merge 3 commits into
mainfrom
orch/divybot-171
Open

fix: add user:sessions:claude_code scope to anthropic_oauth_subscription#576
divybot wants to merge 3 commits into
mainfrom
orch/divybot-171

Conversation

@divybot
Copy link
Copy Markdown
Contributor

@divybot divybot commented May 23, 2026

Summary

Adds user:sessions:claude_code to the scopes anthropic_oauth_subscription requests during its OAuth handshake. The gateway-stored bearer that the credential plugin swaps into outbound Authorization headers at MITM time now carries the scope Anthropic's session-register endpoint requires for /remote-control.

That single-scope change is the whole fix — no clawpatrol-side shim, no synthesized credentials file, no claude /login, no operator-provisioned ~/.claude/.credentials.json. The hypothesis under test (per review): with the upstream bearer carrying the right scope, Claude Code's /remote-control gate is satisfied even when ANTHROPIC_AUTH_TOKEN is in the worker env — the local "bearer-proxy mode disables OAuth features" behavior is just Anthropic's local mirror of the same server-side scope check (anthropics/claude-code#33105).

Previous push (6ecd5ce) layered an installClaudeCodeOAuthShim that synthesized a fake $CLAUDE_CONFIG_DIR/.credentials.json and stripped ANTHROPIC_AUTH_TOKEN from the wrapped child's env. That was over-engineered if the scope-only fix works; this push (6394921) deletes the shim and ships the minimal change.

Operator impact

  • ANTHROPIC_AUTH_TOKEN env pushdown is unchanged from main.
  • Operators who connected the OAuth subscription credential before this PR need to reconnect the credential through the dashboard once so the stored refresh token picks up the new user:sessions:claude_code grant.
  • No new env vars, no new flags, no behavior change for non-Claude-Code consumers (Python SDK, Node SDK, …).

Test plan

  • go build ./... clean
  • go vet ./... clean
  • gofmt -l . clean
  • go test -run TestDaemon ./cmd/clawpatrol/... passes (no regressions from the scope edit)
  • On a worker with the credential reconnected: clawpatrol run claude/remote-control succeeds end-to-end against an anthropic_oauth_subscription-bound profile. If Claude Code's gate turns out to require something beyond the upstream scope, the shim from 6ecd5ce is the fallback.

Closes denoland/orchid#171

Pushing ANTHROPIC_AUTH_TOKEN forces Claude Code into bearer-proxy
mode (precedence #2 in the Anthropic auth ladder), which suppresses
the subscription OAuth credentials from `/login` (precedence #6) and
disables OAuth-only features like `/remote-control` that require the
`user:sessions:claude_code` scope. See anthropics/claude-code#35407
and #48378.

The gateway's MITM rewrite of the Authorization header still works
without the env var. Workers that have run `claude /login` (or have
`~/.claude/.credentials.json` provisioned) send their own subscription
bearer, which `InjectHTTP` overwrites with the operator's stored
bearer before forwarding upstream.

Co-Authored-By: Divy Srivastava <me@littledivy.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 23, 2026

🚀 Preview deployed: https://demo.clawpatrol.dev/pr-preview/pr-576/

Built from 63949217368c3f667196ec1acbe9fc882d503a91 at 2026-05-23T06:37:55Z.

@divybot divybot changed the title Research Claude Code OAuth support for env pushdown fix: stop pushing ANTHROPIC_AUTH_TOKEN from anthropic_oauth_subscription May 23, 2026
@littledivy
Copy link
Copy Markdown
Member

@divybot This is wrong. It must work without claude /login - it's a bug in claude that they don't enable oauth features for ANTHROPIC_AUTH_TOKEN accounts. Find a workaround we could use in clawpatrol

Reverts the prior approach (stop pushing ANTHROPIC_AUTH_TOKEN) and
adds a clawpatrol-side workaround so workers preserve Claude Code
OAuth-only features without needing `claude /login` or a
hand-provisioned `~/.claude/.credentials.json`.

The new shim lives in `clawpatrol run`: when the wrapped command is
`claude` and ANTHROPIC_AUTH_TOKEN is in the env, it synthesizes a
`.credentials.json` under a clawpatrol-managed CLAUDE_CONFIG_DIR with
the placeholder bearer and `user:sessions:claude_code` in the scopes
list, then strips ANTHROPIC_AUTH_TOKEN so Claude Code's local
auth-mode gate falls through to precedence #6 (subscription OAuth
from /login) and exposes `/remote-control`. The gateway's MITM still
rewrites Authorization upstream, so the placeholder bytes never
reach Anthropic. Non-Claude-Code SDK clients (Python, Node, …) still
see ANTHROPIC_AUTH_TOKEN unchanged. Operators can disable the shim
with `CLAWPATROL_NO_CLAUDE_OAUTH_SHIM=1`.

Also adds `user:sessions:claude_code` to the OAuth subscription
plugin's requested scopes — without it the upstream session-register
call fails with `OAuth token does not meet scope requirement
user:sessions:claude_code` (anthropics/claude-code#33105) even when
the local feature flag is satisfied.

Co-Authored-By: Divy Srivastava <me@littledivy.com>
@divybot divybot changed the title fix: stop pushing ANTHROPIC_AUTH_TOKEN from anthropic_oauth_subscription fix: shim Claude Code OAuth via CLAUDE_CONFIG_DIR for /remote-control May 23, 2026
@divybot
Copy link
Copy Markdown
Contributor Author

divybot commented May 23, 2026

Re: workers running without claude /login — the latest push (6ecd5ce) drops the original "tell workers to /login" direction entirely and replaces it with a clawpatrol-side workaround that fires automatically inside clawpatrol run:

  • installClaudeCodeOAuthShim(cmd) runs after applyEnvPushdownVars. When cmd[0]'s basename is claude and ANTHROPIC_AUTH_TOKEN is in the env, it synthesizes $CLAUDE_CONFIG_DIR/.credentials.json with the placeholder bearer plus scopes ["user:inference", "user:profile", "user:sessions:claude_code"] + subscriptionType: "max" + a year-2286 expiresAt, sets CLAUDE_CONFIG_DIR to that dir, and strips ANTHROPIC_AUTH_TOKEN from the child's env.
  • Result: Claude Code's local auth ladder skips precedence Use sqlite database for gateway #2 (bearer-proxy) and lands on Add landing page and docs site #6 (subscription OAuth from /login). The local /remote-control gate sees user:sessions:claude_code in the loaded credential's scopes and stays on.
  • The gateway's MITM rewrite of Authorization continues to swap the placeholder for the operator's stored OAuth bearer upstream — same as before.
  • Wrapped commands that aren't claude (Python SDK, Node, …) see ANTHROPIC_AUTH_TOKEN untouched.
  • Opt-out: CLAWPATROL_NO_CLAUDE_OAUTH_SHIM=1.

So no claude /login and no operator-provisioned ~/.claude/.credentials.json are required — clawpatrol synthesizes the file on the fly during clawpatrol run. Worker setup is unchanged.

The Anthropic-side counterpart: I also added user:sessions:claude_code to AnthropicOAuthSubscription.OAuthFlow().Scopes so the operator's stored bearer carries the scope needed for the upstream session-register call (anthropics/claude-code#33105). Operators who connected the OAuth subscription credential before this PR need to reconnect once to pick up the new grant.

Tests in cmd/clawpatrol/integrations_test.go cover the happy path (file contents, 0600 mode, scope list, env mutation) and the four no-op cases (opt-out, empty argv, non-claude binary, no ANTHROPIC_AUTH_TOKEN).

@littledivy
Copy link
Copy Markdown
Member

Don't like installClaudeCodeOAuthShim. Does just user:sessions:claude_code and ANTHROPIC_AUTH_TOKEN fix the issue?

Per review: remove the `installClaudeCodeOAuthShim` machinery and the
synthesized `CLAUDE_CONFIG_DIR/.credentials.json` it materialized. The
hypothesis to test is that Claude Code's `/remote-control` gate is
satisfied by the upstream bearer carrying `user:sessions:claude_code`
— no local shim required when the gateway's MITM swaps the
Authorization header in.

What's left:
- `anthropic_oauth_subscription.OAuthFlow().Scopes` keeps the
  newly-added `user:sessions:claude_code` so the operator's stored
  bearer can pass Anthropic's session-register scope check
  (anthropics/claude-code#33105). Existing operators reconnect the
  credential once to pick up the new grant.
- `ANTHROPIC_AUTH_TOKEN` env pushdown is unchanged from main.

Co-Authored-By: Divy Srivastava <me@littledivy.com>
@divybot divybot changed the title fix: shim Claude Code OAuth via CLAUDE_CONFIG_DIR for /remote-control fix: add user:sessions:claude_code scope to anthropic_oauth_subscription May 23, 2026
@divybot
Copy link
Copy Markdown
Contributor Author

divybot commented May 23, 2026

Stripped the shim in 6394921. PR now just adds user:sessions:claude_code to the OAuth scopes — the diff is 5 lines of code plus comments. If the upstream-bearer scope alone unblocks /remote-control, that's the whole fix. If it turns out Claude Code's local mode-bit gate is also blocking, the shim from 6ecd5ce is in git history as the next thing to try.

Worth flagging: operators who already connected the OAuth subscription credential will need to reconnect once so the stored refresh token picks up the new user:sessions:claude_code grant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants