Skip to content

Feat/anthropic subscription oauth#2285

Open
cjfit wants to merge 6 commits into
NVIDIA:mainfrom
cjfit:feat/anthropic-subscription-oauth
Open

Feat/anthropic subscription oauth#2285
cjfit wants to merge 6 commits into
NVIDIA:mainfrom
cjfit:feat/anthropic-subscription-oauth

Conversation

@cjfit

@cjfit cjfit commented Jul 15, 2026

Copy link
Copy Markdown

Summary

Adds an anthropic-oauth provider type (alias: claude-subscription) that lets sandboxed agents run Claude Code against an Anthropic Pro/Max subscription instead of an API key. The OAuth access token is held gateway-side and injected only at the egress boundary (inference.local) and it never enters the sandbox environment, filesystem, or logs.

Related Issue

Closes #1925

Human Notes

  • Claude Fable 5 was used to help prepare the changes and PR, but all changes were prompted and reviewed by a human.
  • I chose to edit existing GCP provider functions because they already implemented functionality that would otherwise need to be duplicated for Anthropic OAuth (e.g. ensuring env vars have real values at sandbox initialization).
  • I used providers v2 as it seems this is the path that the project is standardizing on, and it handles OAuth refreshes well.
  • I chose not to make this fully "magic" (i.e. doing auto-discovery and initialization of the provider without the user explicitly opting in). I'm open to thoughts here, but this seems a bit intrusive due to the fact that the Claude OAuth token is read from the user's Keychain on macOS. Incorporating into the main README in a future PR with instructions could help users do the setup (currently 3 lines).

These screenshots show how the new provider works in a new sandbox. osl is an alias for my locally-compiled OpenShell executable. Providers v2 must be enabled first: osl settings set --global --key providers_v2_enabled --value true --yes.

Screenshot 2026-07-14 at 9 04 49 PM Screenshot 2026-07-14 at 9 04 35 PM

Changes

  • New provider plugin (openshell-providers): anthropic-oauth with claude-subscription alias. Seeds sandbox env with ANTHROPIC_BASE_URL=https://inference.local and an ANTHROPIC_AUTH_TOKEN placeholder. Deliberately does not inject ANTHROPIC_API_KEY — Claude Code interrupts startup to confirm env API keys, but accepts ANTHROPIC_AUTH_TOKEN silently; the router strips both headers at egress anyway and injects the real subscription Bearer token plus the oauth-2025-04-20 beta header, so the two are wire-identical.
  • Static-config env resolution (openshell-core): the credential pipeline placeholderizes all provider env values, but SDKs must parse ANTHROPIC_BASE_URL at process startup. child_env_with_gcp_resolved is generalized to child_env_with_static_config_resolved and now resolves non-secret Anthropic config keys back to real values in the sandbox env, following the existing GCP pattern. Auth tokens stay egress-time placeholders. (Call sites and a doc-comment in google_cloud_metadata.rs updated for the rename.)
  • CLI setup flow (openshell-cli): openshell provider create --from-claude-login harvests the subscription OAuth material from the host keychain; --name/--type now default to claude-subscription/anthropic-oauth when that flag is set, a default inference route is configured if none exists, and the missing-provider message during sandbox create points at the correct setup command (the credentials can't be configured from inside a sandbox).
  • Token refresh (openshell-server): the refresh worker rotates the access token ahead of expiry and persists rotated refresh tokens. When the token endpoint rejects the refresh grant with HTTP 4xx (revoked login, lapsed plan), the stored error now carries recovery guidance (claude login on the host, recreate the provider); transient network errors and 5xx are excluded.
  • Docs: provider guide (docs/providers/anthropic-subscription.mdx) covering setup, the env preset, why Claude Code's "API Usage Billing" banner is cosmetic (billing goes to the subscription), and refresh-failure recovery; architecture/gateway.md updated for the env projection behavior.

Testing

  • Unit suites green: provider plugin, provider_credentials static-config resolution, gRPC resolve_provider_environment (asserts the access token is never injected into sandbox env), and provider refresh (re-login hint applies only to anthropic-oauth 4xx grant rejections).
  • Manual golden path on a locally built gateway: provider create --from-claude-login → sandbox create --provider claude-subscription → bare claude inside the sandbox authenticates without prompts and answers requests; Console API usage stays flat (billing hits the subscription).
  • mise run pre-commit green.

Local test checklist

Prereqs: Docker running, a Claude Pro/Max subscription with claude login completed on the host.

  • Build: cargo build -p openshell-cli and mise run gateway:docker (rebuilds the gateway + supervisor image — required, the env injection changed)
  • Lint/format: mise run pre-commit
  • Unit tests: mise run test (note: ssh::tests::wait_for_forward_listener_rejects_missing_listener and sbom test_same_domain_requests_are_spaced are pre-existing flakes under parallel load; both pass isolated)
  • e2e: mise run e2e
  • Golden path:
    • openshell provider create --from-claude-login — succeeds without --name/--type, prints the created claude-subscription provider and default inference route
    • openshell sandbox create --provider claude-subscription then run bare claude inside — no login prompt, no "Do you want to use this API key?" prompt, responds to a message
    • Inside the sandbox: echo $ANTHROPIC_AUTH_TOKEN shows an openshell:resolve:env:... placeholder (never a real token); echo $ANTHROPIC_BASE_URL shows https://inference.local; ANTHROPIC_API_KEY is unset
    • openshell provider refresh status shows the token rotating
  • Negative path: openshell sandbox create --provider claude-subscription before creating the provider — error poim-claude-login`

One caveat for macOS reviewers: a pre-existing bash 3.2 empty-array bug in e2e/mcp-conformance.sh (unrelated to this PR)

Checklist

  • Conventional Commits, DCO sign-off
  • Architecture and published docs updated
  • e2e run on final revision

cjfit added 6 commits July 14, 2026 15:04
Add an `anthropic-oauth` provider type (alias `claude-plan`) so sandboxes can
run Claude Code on an Anthropic Pro/Max subscription. The OAuth token is
harvested, stored, and refreshed entirely outside the sandbox and injected only
at the egress boundary; it is never written into the sandbox environment,
filesystem, or logs.

- inference: new anthropic-oauth profile (Authorization: Bearer plus a mandatory
  anthropic-beta: oauth-2025-04-20 default header)
- router: merge the mandatory anthropic-beta flag with any client-sent value so
  the sandbox cannot drop it
- server: mark ANTHROPIC_OAUTH_TOKEN non-injectable (proxy-only)
- cli: add `provider create --from-claude-login` to read the local Claude Code
  login (macOS Keychain or ~/.claude/.credentials.json) and wire the gateway's
  OAuth2 background refresh
- docs: add the Anthropic Subscription provider page and update gateway
  architecture and provider tables

The macOS path (Keychain) is validated end-to-end. Linux reads the same
~/.claude/.credentials.json file and should work unchanged, but has not yet been
validated on Linux.

Signed-off-by: Cedric Fitzgerald <soulcedric2@gmail.com>
Project ANTHROPIC_BASE_URL and a placeholder ANTHROPIC_AUTH_TOKEN into
sandboxes bound to an anthropic-oauth provider so agent CLIs reach the
gateway inference endpoint without manual configuration, login flows, or
API-key confirmation prompts. The auth token is a non-secret constant;
inference.local replaces caller auth with the real subscription token at
the egress boundary.

Resolve ANTHROPIC_BASE_URL to its real value in the sandbox env: the
credential pipeline placeholderizes all provider env values, and a
placeholderized URL cannot be parsed by SDKs at process startup. Auth
values stay egress-time placeholders. Renames
child_env_with_gcp_resolved to child_env_with_static_config_resolved
since it now covers non-GCP static config.

Signed-off-by: Cedric Fitzgerald <soulcedric2@gmail.com>
Make --name and --type optional for provider create with
--from-claude-login, defaulting to claude-subscription/anthropic-oauth.
After creating the provider, configure the user inference route
automatically when none is set (best-effort, unverified), removing the
separate inference set step.

When sandbox create hits a missing anthropic-oauth provider, stop
offering env-credential discovery (subscription login material lives in
the host keychain and cannot be configured from inside a sandbox) and
point at provider create --from-claude-login instead.

Signed-off-by: Cedric Fitzgerald <soulcedric2@gmail.com>
When the anthropic-oauth token endpoint rejects the refresh grant with HTTP 4xx (revoked login, lapsed plan), append recovery guidance to the stored refresh error so provider refresh status tells the operator to re-authenticate on the host and recreate the provider. Transient network errors and 5xx responses are excluded.

Signed-off-by: Cedric Fitzgerald <soulcedric2@gmail.com>
…d recovery

Describe the ANTHROPIC_AUTH_TOKEN env preset and why an API key is not injected, note that the API-usage billing banner in Claude Code is cosmetic while billing goes to the subscription, and add recovery steps for a rejected refresh token. Update the gateway architecture doc to match the env projection behavior.

Signed-off-by: Cedric Fitzgerald <soulcedric2@gmail.com>
@cjfit cjfit requested review from a team, derekwaynecarr, maxamillion and mrunalp as code owners July 15, 2026 01:18
@copy-pr-bot

copy-pr-bot Bot commented Jul 15, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown

All contributors have signed the DCO ✍️ ✅
Posted by the DCO Assistant Lite bot.

@cjfit

cjfit commented Jul 15, 2026

Copy link
Copy Markdown
Author

I have read the DCO document and I hereby sign the DCO.

@cjfit

cjfit commented Jul 15, 2026

Copy link
Copy Markdown
Author

recheck

@purp

purp commented Jul 15, 2026

Copy link
Copy Markdown

Personally super excited that you're tackling this! It was on my list of annoyances to fix once I had a bit more understanding of the provider system.

Note that CLAUDE_CODE_OAUTH_TOKEN is also an environment variable which Claude will recognize for a long-lived (1 year) token as noted in their authentication documentation.

As noted in #620 you can create a provider based on it which is recognized by claude ... CLI commands in a sandbox, but starting a claude type sandbox still forces a copy-paste web login. To give that (also broken) flow a try:

openshell provider create --name anthropic --type claude --credential CLAUDE_CODE_OAUTH_TOKEN
openshell sandbox create --name claude-is-sad --provider anthropic -- claude 
# ... and then ignore the Claude setup screen which will want you to pick a login method
# instead, in another shell:
openshell sandbox exec --name claude-is-sad -- bash -lc 'echo $(claude auth status)'
# returns: { "loggedIn": true, "authMethod": "oauth_token", "apiProvider": "firstParty" }

Looking forward to this!

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.

feat: oauth support for Claude Pro/Max subscription plans

2 participants