Skip to content

feat(auth): add Claude subscription sign-in (claude/<model>, no API key) - #1333

Draft
calvinchengx wants to merge 1 commit into
usestrix:mainfrom
calvinchengx:feat/claude-subscription-auth
Draft

calvinchengx wants to merge 1 commit into
usestrix:mainfrom
calvinchengx:feat/claude-subscription-auth

Conversation

@calvinchengx

@calvinchengx calvinchengx commented Sep 18, 2026

Copy link
Copy Markdown

Summary

Adds a Claude Pro/Max subscription sign-in, the direct analogue of the existing strix auth login chatgpt (Codex) path — OAuth 2.0 + PKCE against Anthropic, tokens stored in ~/.strix/subscription-auth.json, claude/<model> as a STRIX_LLM.

Important

Read this before reviewing. The OAuth authentication here works end-to-end, but the path cannot currently perform inference without impersonating Claude Code — which this PR deliberately does not do. See "Root-cause finding" below. I'm opening it as a draft for maintainer discussion, not as a merge-ready usable path.

How it works

  • strix/config/claude.py — Anthropic OAuth 2.0 + PKCE, a token store shared with the Codex path (keyed per provider), a cross-process refresh guard, and oauth_api_key().
  • strix/config/subscription.py — a provider-agnostic facade so telemetry, cost reporting, and env validation treat both subscriptions uniformly.
  • Model routing (strix/config/models.py) — claude/<slug> builds a LiteLLM anthropic/<slug> model. LiteLLM detects the sk-ant-oat… token prefix and switches to Authorization: Bearer + the oauth-2025-04-20 beta header, so no custom translation is needed; a thin subclass re-reads the token per request so long runs survive expiry.
  • CLI / validationstrix auth login|status|logout are provider-aware (chatgpt/claude, with codex/anthropic aliases); env validation and the expired-token hint recognise both.

What is verified working

  • Sign-in stores a valid record (access sk-ant-oat…, refresh sk-ant-ort…, account email).
  • Refresh — an expired access token was auto-renewed on a later run and persisted.
  • The token authenticates — a manual Messages-API probe returned HTTP 200 with healthy quota headers.

⚠️ Root-cause finding: inference is gated behind Claude Code identity, NOT rate limits

Testing against the live backend, every inference call from Strix returned 429 rate_limit_error (message "Error", no rate-limit headers). That looks like throttling, but it is not. Two byte-identical requests, differing only in one field:

Request Result
OAuth token + beta header, no system prompt (Strix's normal call) 429 rate_limit_error, "Error", no headers
Same + first system block = "You are Claude Code, Anthropic's official CLI for Claude." HTTP 200, real completion, quota headers show the account is allowed (~28%/52% used)

So the account has ample quota; Anthropic simply only serves inference on a Claude Code OAuth token when the request presents itself as Claude Code (the exact system-prompt identity string). The misleading 429 is a disguised access-control rejection of non-Claude-Code callers.

Consequence for this PR: the claude/ route sends Strix's own pentesting system prompts, not the Claude Code identity, so it authenticates but is refused inference. Making it work would require injecting the Claude Code system prompt on every request — i.e. impersonating Claude Code to defeat an Anthropic anti-abuse control, and corrupting Strix's own agent identity in the process. This PR intentionally does not do that.

Why it's still opened

The OAuth plumbing (login/exchange/refresh/env-wiring) is correct, tested, and mirrors the accepted Codex design — so it's a sound base if the project decides how it wants to handle Anthropic subscriptions. But as-is, a claude/ model cannot run a scan; a metered anthropic/ API key remains the working path. Maintainers should decide whether to (a) keep this as auth-only scaffolding, (b) close it, or (c) take a documented position on the identity requirement — given the ToS considerations, I did not want to make that call by shipping the impersonation.

Tests

  • tests/test_claude_auth.py — PKCE, authorize URL, JSON token exchange (incl. state), refresh + rotation, store round-trip, subscription-model routing, and the shared facade.
  • tests/test_auth_cli.py — provider resolution/aliases, Claude login flow, manual-only callback, per-provider logout.
  • tests/test_models.pyclaude/… routes to a LiteLLM anthropic/… model and refreshes the bearer per request.

ruff, ruff format, mypy, bandit clean; full suite passes (one pre-existing, unrelated test_pricing.py grok-routing failure tied to the installed LiteLLM data).

Run Strix on a Claude Pro/Max subscription with STRIX_LLM="claude/<model>"
and no API key, mirroring the existing ChatGPT (Codex) subscription path.

- strix/config/claude.py: Anthropic OAuth 2.0 + PKCE, shared token store,
  cross-process refresh guard, per-request token via oauth_api_key().
- strix/config/subscription.py: provider-agnostic facade so telemetry,
  cost reporting, and env validation treat both subscriptions uniformly.
- models.py routes claude/<slug> through LiteLLM's anthropic provider,
  which detects the sk-ant-oat token prefix and sends it as an OAuth bearer
  with the oauth beta header; the token is refreshed on every request.
- strix auth login/status/logout, environment validation, and the expired
  -token hint are provider-aware. claude/ = subscription; anthropic/ = key.

The Anthropic OAuth constants are reverse-engineered from Claude Code and,
like the Codex path, using a subscription outside the vendor's own apps is
not officially supported. Needs a live sign-in to verify the handshake.

This branch has not been deployed

No deployments
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.

1 participant