fix(anthropic): restore direct OAuth execution for Claude subscriptions#1862
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR restores direct Anthropic OAuth execution on the built-in provider, removes ChangesAnthropic OAuth Direct-Execution Revert
Claude Sonnet 5 Catalog Restore
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Claude subscription (Max/Pro) chats regressed to 404/502/429 because FN-7396 rerouted subscription OAuth to a /v1-based `anthropic-subscription` runtime provider — reintroducing issue #1857 that FN-7391 had fixed. Both routed the OAuth token to api.anthropic.com/v1, the surface that broke. Proven in code that v0.51.0 (working) sent subscription OAuth directly to /v1 via pi-ai's built-in `anthropic` provider (Claude Code impersonation: Bearer + anthropic-beta oauth headers), NOT through the CLI. Restore that: - auth-storage: getApiKey("anthropic") resolves subscription/legacy OAuth again (raw API key still wins), so the built-in provider gets the token. - pi.ts: remove the runtime reroute and the /v1 `anthropic-subscription` execution provider so anthropic/* selections stay on the built-in provider. - register-model-routes: advertise `anthropic` for OAuth users so direct OAuth is selectable in the picker. Three independent surfaces, no rerouting: direct OAuth, raw ANTHROPIC_API_KEY (precedence), and explicit pi-claude-cli. Fixes #1857 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ba98615 to
a1af5de
Compare
Greptile SummaryThis PR reverts the FN-7391/FN-7396 regression that had re-introduced the #1857 subscription-chat failure (404/502/429 cascade) by restoring the v0.51.0 direct OAuth execution path for Claude subscription users.
Confidence Score: 5/5Safe to merge — the fix correctly restores the v0.51.0 direct OAuth execution path and is backed by 137 engine tests and 352 dashboard tests passing. The core logic change — resolving subscription and legacy OAuth credentials in resolveAnthropicRuntimeApiKey and removing the broken anthropic-subscription reroute in pi.ts — is well-reasoned, thoroughly tested, and matches the stated v0.51.0 baseline. Logout guard symmetry is confirmed correct by the existing test suite. The two observations are robustness/UX nits that do not affect correctness of the main OAuth or API-key paths. The file-scan branch in register-model-routes.ts (lines 89–92) and the claude-sonnet-5 supplemental entry in anthropic-models.ts are worth a second look, but neither represents a blocking issue. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant User
participant Dashboard as Dashboard /api/models
participant RegRoutes as register-model-routes
participant AuthStore as auth-storage
participant PiAgent as pi.ts / createFnAgent
participant PiAI as pi-ai built-in provider
participant AnthropicAPI as api.anthropic.com/v1
User->>Dashboard: GET /api/models
Dashboard->>RegRoutes: getConfiguredProviderNames()
RegRoutes->>AuthStore: hasAuth("anthropic") OR hasAuth("anthropic-subscription")
AuthStore-->>RegRoutes: true (OAuth or API key present)
RegRoutes-->>Dashboard: providers includes "anthropic"
Dashboard-->>User: model list (incl. anthropic/claude-sonnet-5)
User->>PiAgent: "createFnAgent({provider:"anthropic", model:"claude-sonnet-5"})"
PiAgent->>PiAgent: mergeSupplementalAnthropicModels()
Note over PiAgent: No registerAnthropicSubscriptionProvider() call
PiAgent->>AuthStore: getApiKey("anthropic")
AuthStore->>AuthStore: resolveAnthropicRuntimeApiKey()
Note over AuthStore: Precedence: raw API key → legacy OAuth → subscription OAuth → fallback
AuthStore-->>PiAgent: OAuth access token (or raw key)
PiAgent->>PiAI: "createAgentSession({model:{provider:"anthropic", id:"claude-sonnet-5"}})"
PiAI->>AnthropicAPI: POST /v1/messages (Bearer OAuth + Claude Code headers)
AnthropicAPI-->>PiAI: 200 OK (API key) or 403 scope (OAuth+Sonnet5)
PiAI-->>User: response or actionable-failure fallback
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant User
participant Dashboard as Dashboard /api/models
participant RegRoutes as register-model-routes
participant AuthStore as auth-storage
participant PiAgent as pi.ts / createFnAgent
participant PiAI as pi-ai built-in provider
participant AnthropicAPI as api.anthropic.com/v1
User->>Dashboard: GET /api/models
Dashboard->>RegRoutes: getConfiguredProviderNames()
RegRoutes->>AuthStore: hasAuth("anthropic") OR hasAuth("anthropic-subscription")
AuthStore-->>RegRoutes: true (OAuth or API key present)
RegRoutes-->>Dashboard: providers includes "anthropic"
Dashboard-->>User: model list (incl. anthropic/claude-sonnet-5)
User->>PiAgent: "createFnAgent({provider:"anthropic", model:"claude-sonnet-5"})"
PiAgent->>PiAgent: mergeSupplementalAnthropicModels()
Note over PiAgent: No registerAnthropicSubscriptionProvider() call
PiAgent->>AuthStore: getApiKey("anthropic")
AuthStore->>AuthStore: resolveAnthropicRuntimeApiKey()
Note over AuthStore: Precedence: raw API key → legacy OAuth → subscription OAuth → fallback
AuthStore-->>PiAgent: OAuth access token (or raw key)
PiAgent->>PiAI: "createAgentSession({model:{provider:"anthropic", id:"claude-sonnet-5"}})"
PiAI->>AnthropicAPI: POST /v1/messages (Bearer OAuth + Claude Code headers)
AnthropicAPI-->>PiAI: 200 OK (API key) or 403 scope (OAuth+Sonnet5)
PiAI-->>User: response or actionable-failure fallback
Reviews (3): Last reviewed commit: "Align pi-claude-cli peer deps to pi-ai 0..." | Re-trigger Greptile |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
packages/engine/src/auth-storage.ts (1)
518-521: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTighten this FNXC note to the requirement.
The FNXC body is doing historical/proof narration instead of concise technical requirement prose. Keep the detailed regression story in the changeset/docs and make the code comment state the invariant.
♻️ Proposed comment trim
/* FNXC:ProviderAuth 2026-07-01-14:55: - Restore the v0.51.0 direct-OAuth execution path (regressed by FN-7291 → FN-7391 → FN-7396, all built on issue `#1857`'s incorrect "Anthropic blocks subscription OAuth on /v1, must use the CLI" conclusion). PROVEN in code: at v0.51.0 `getApiKey("anthropic")` returned the subscription OAuth access token, and pi-ai's built-in `anthropic` provider POSTs it to `api.anthropic.com/v1/messages` with full Claude Code impersonation (`Authorization: Bearer` + `anthropic-beta: claude-code-20250219,oauth-2025-04-20`), which Anthropic accepts. So `anthropic` runtime auth resolves, in precedence order: (1) raw API key, (2) legacy `anthropic` OAuth, (3) separated `anthropic-subscription` OAuth, (4) models.json / ModelRegistry fallback raw key. Raw key still wins so an explicit `ANTHROPIC_API_KEY` keeps using x-api-key. Direct OAuth, raw API key, and explicit `pi-claude-cli` remain three independent surfaces. + Anthropic runtime auth must preserve raw API-key precedence, then resolve legacy `anthropic` OAuth and separated `anthropic-subscription` OAuth on the built-in provider, with models.json/ModelRegistry raw-key fallback last. + Keep explicit `pi-claude-cli` as a separate provider surface. */As per coding guidelines, FNXC comments in
**/*.{ts,tsx,js,jsx,mjs,cjs}must useFNXC:<Area-of-product>, include ayyyy-MM-dd-hh:mmtimestamp, and encode the requirement/change in concise technical prose.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/engine/src/auth-storage.ts` around lines 518 - 521, Rewrite the FNXC comment in auth-storage.ts to state only the invariant in concise technical prose, keeping the required FNXC:ProviderAuth tag and yyyy-MM-dd-hh:mm timestamp. Remove the historical/proof narration and long regression explanation, and summarize the auth resolution order and precedence around getApiKey("anthropic") / Anthropic runtime auth in a short requirement-style comment.Source: Coding guidelines
packages/engine/src/pi.ts (1)
1174-1177: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winShorten this FNXC comment to the routing invariant.
This comment captures the right behavior, but it is too narrative for an FNXC requirement note.
♻️ Proposed comment trim
/* FNXC:ProviderAuth 2026-07-01-14:55: -Anthropic has three independent surfaces and NO runtime rerouting between them: (1) direct OAuth — a subscription/OAuth `anthropic/<model>` selection executes on pi-ai's built-in `anthropic` provider, which detects the `sk-ant-oat` token and POSTs to `api.anthropic.com/v1/messages` with full Claude Code impersonation (the v0.51.0 working path; `authStorage.getApiKey("anthropic")` supplies the OAuth token); (2) raw API key — a configured `ANTHROPIC_API_KEY` takes precedence in `getApiKey("anthropic")` and uses x-api-key on the same built-in provider; (3) Claude CLI — an explicit `pi-claude-cli/<model>` selection runs through the vendored CLI extension. FN-7291/FN-7391/FN-7396 added an `/v1`-based `anthropic-subscription` reroute on the incorrect premise that Anthropic blocks subscription OAuth on `/v1`; that reroute is intentionally absent so direct OAuth is not re-broken (issue `#1857`). +Anthropic has three independent execution surfaces: built-in `anthropic` for OAuth or raw API-key auth, and explicit `pi-claude-cli` for CLI-backed execution. +Do not register or route through an `anthropic-subscription` provider at runtime. */As per coding guidelines, FNXC comments in
**/*.{ts,tsx,js,jsx,mjs,cjs}must useFNXC:<Area-of-product>, include ayyyy-MM-dd-hh:mmtimestamp, and encode the requirement/change in concise technical prose.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/engine/src/pi.ts` around lines 1174 - 1177, The FNXC note in pi.ts is too narrative and should be trimmed to the routing invariant only. Rewrite the existing ProviderAuth comment near the Anthropic routing logic to a concise requirement statement that preserves the key rule: direct OAuth and raw API key must stay on the built-in anthropic provider, and only explicit pi-claude-cli/<model> should use the CLI path. Keep the FNXC prefix, timestamp, and a short technical description tied to the Anthropic selection/routing behavior.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/engine/src/auth-storage.ts`:
- Around line 518-521: Rewrite the FNXC comment in auth-storage.ts to state only
the invariant in concise technical prose, keeping the required FNXC:ProviderAuth
tag and yyyy-MM-dd-hh:mm timestamp. Remove the historical/proof narration and
long regression explanation, and summarize the auth resolution order and
precedence around getApiKey("anthropic") / Anthropic runtime auth in a short
requirement-style comment.
In `@packages/engine/src/pi.ts`:
- Around line 1174-1177: The FNXC note in pi.ts is too narrative and should be
trimmed to the routing invariant only. Rewrite the existing ProviderAuth comment
near the Anthropic routing logic to a concise requirement statement that
preserves the key rule: direct OAuth and raw API key must stay on the built-in
anthropic provider, and only explicit pi-claude-cli/<model> should use the CLI
path. Keep the FNXC prefix, timestamp, and a short technical description tied to
the Anthropic selection/routing behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9c371bca-7a1d-4bd8-80ce-413120e46244
📒 Files selected for processing (9)
.changeset/fix-anthropic-oauth-direct-access.mddocs/dashboard-guide.mddocs/settings-reference.mdpackages/dashboard/src/__tests__/routes-auth.test.tspackages/dashboard/src/routes/register-model-routes.tspackages/engine/src/__tests__/auth-storage.test.tspackages/engine/src/__tests__/pi-create-fn-agent.test.tspackages/engine/src/auth-storage.tspackages/engine/src/pi.ts
…1862) Sonnet 5 had disappeared from every surface: pi-ai 0.79.9 (the installed version) lacks it, and FN-7374 removed the static row expecting the live registry to carry it. Live-verified that claude-sonnet-5 returns 200 on api.anthropic.com/v1 with a raw ANTHROPIC_API_KEY and runs via the Claude CLI (it 403s on subscription-OAuth /v1 — scope-gated; runtime fallback applies). Note: pi-ai 0.80.3 ships sonnet-5 natively, so this SUPPLEMENTAL row dedupes once the install catches up. - core: re-add claude-sonnet-5 to SUPPLEMENTAL_ANTHROPIC_PROVIDER_REGISTRATION and restore its static pricing (revert FN-7374); update pricing tests. - engine/dashboard tests: flip the FN-7374 "withheld" assertions to the restored "advertised" behavior. PR feedback: - Trim the two FNXC comments (auth-storage.ts, pi.ts) to concise requirement prose per coding guidelines (CodeRabbit). - Replace the now-inert getApiKey mock in two subscription routing tests with a clarifying note (Greptile). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed the review nitpicks in b7c6443: CodeRabbit — "Tighten this FNXC note to the requirement" (auth-storage.ts) and "Shorten this FNXC comment to the routing invariant" (pi.ts): Greptile — "Dead mock setup after routing removal" (pi-create-fn-agent.test.ts): Also in this push (separate from the OAuth fix, surfaced during verification): restored Claude Sonnet 5 to the model picker — it had dropped off every surface (installed pi-ai 0.79.9 lacks it and FN-7374 removed the static row). Live-verified it returns 200 via raw API key and runs via the Claude CLI. Note: pi-ai 0.80.3 ships Sonnet 5 natively, so the SUPPLEMENTAL row dedupes once the install catches up. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/core/src/__tests__/model-pricing.test.ts (1)
39-54: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winTighten assertions to an exact cost value.
Sibling tests in this file (lines 27-37, 56-66) assert exact
toBeCloseTovalues rather than just positivity, giving stronger regression protection. With the given usage and Sonnet 5 rates, the expected cost is 5.1.♻️ Proposed tightened assertions
const anthropic = costFor(usage, { provider: "anthropic", model: "claude-sonnet-5" }); expect(anthropic.unavailable).toBe(false); - expect(anthropic.usd).toBeGreaterThan(0); + expect(anthropic.usd).toBeCloseTo(5.1, 2); const bare = costFor(usage, { model: "claude-sonnet-5" }); expect(bare.unavailable).toBe(false); - expect(bare.usd).toBeGreaterThan(0); + expect(bare.usd).toBeCloseTo(5.1, 2);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/__tests__/model-pricing.test.ts` around lines 39 - 54, The Anthropic Claude Sonnet 5 pricing test in model-pricing.test should assert the exact calculated USD amount instead of only checking that it is positive. Update the existing costFor expectations for both the provider-specific and bare model cases to use a close-to exact value of 5.1, matching the style used by the sibling tests in this file and providing stronger regression coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/core/src/__tests__/model-pricing.test.ts`:
- Around line 39-54: The Anthropic Claude Sonnet 5 pricing test in
model-pricing.test should assert the exact calculated USD amount instead of only
checking that it is positive. Update the existing costFor expectations for both
the provider-specific and bare model cases to use a close-to exact value of 5.1,
matching the style used by the sibling tests in this file and providing stronger
regression coverage.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 90710b13-f064-4076-bed1-5a4201462b52
📒 Files selected for processing (8)
.changeset/restore-claude-sonnet-5-catalog.mdpackages/core/src/__tests__/model-pricing.test.tspackages/core/src/anthropic-models.tspackages/core/src/model-pricing.tspackages/dashboard/src/__tests__/routes-auth.test.tspackages/engine/src/__tests__/pi-create-fn-agent.test.tspackages/engine/src/auth-storage.tspackages/engine/src/pi.ts
✅ Files skipped from review due to trivial changes (1)
- .changeset/restore-claude-sonnet-5-catalog.md
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/engine/src/pi.ts
- packages/engine/src/auth-storage.ts
The vendored pi-claude-cli extension declared `@earendil-works/pi-ai` and
`pi-coding-agent` as `*` peers, so its dev/workspace resolution drifted to
0.77.0 while the rest of the workspace is on ^0.80.3. Pin both peers to
^0.80.3 so the extension's `getModels("anthropic")` catalog (which feeds the
pi-claude-cli picker rows) matches the engine/cli — 0.80.3 ships
claude-sonnet-5 natively. Behavior-preserving; the bundled CLI already
provided 0.80.3 at runtime.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Problem
Claude subscription (Max/Pro) chats fail with a rotating
404 / 502 / 429cascade — the same symptom as #1857, which was reported fixed. RawANTHROPIC_API_KEYand explicit Claude CLI usage work; direct OAuth does not.Root cause (proven against git history)
packages/core/src/anthropic-models.tsdidn't exist.getApiKey("anthropic")returned the subscription OAuth access token and pi-ai's built-inanthropicprovider POSTed it toapi.anthropic.com/v1with Claude Code identity headers (Bearer+anthropic-beta: claude-code-20250219,oauth-2025-04-20). Direct OAuth worked — not via the CLI.grepmissed the runtime-appended/v1; a naive curl repro lacked pi-ai's full impersonation). FN-7291 → FN-7391 → FN-7396 were built on that wrong premise. FN-7396'sregisterAnthropicSubscriptionProviderrerouted subscription OAuth to a newanthropic-subscriptionprovider still pointed atapi.anthropic.com/v1— reintroducing 0.52.0 regression: Anthropic *subscription* chat routed to direct api.anthropic.com/v1 (OAuth) → 429/502; 0.51.0 uses the CLI and works #1857.Fix — restore the v0.51.0 path (three independent surfaces, no rerouting)
auth-storage.ts—getApiKey("anthropic")resolves subscription/legacy OAuth again (raw API key still takes precedence), so the built-in provider receives the token.pi.ts— remove the runtime reroute and the/v1-basedanthropic-subscriptionexecution provider;anthropic/*selections stay on the built-in OAuth-capable provider.register-model-routes.ts— advertiseanthropicin the picker for OAuth users so direct OAuth is selectable.ANTHROPIC_API_KEY(precedence), and explicitpi-claude-cliall work independently.Verification
packages/engine:auth-storage.test.ts+pi-create-fn-agent.test.ts— 137 passed (assertions restored to the correct invariant; the FN-7391/FN-7396 tests encoded the bug).packages/dashboard:routes-auth.test.ts+usage.test.ts— 352 passed.pnpm --filter @fusion/engine buildclean.settings-reference.md,dashboard-guide.md) + changeset updated.Fixes #1857
🤖 Generated with Claude Code
Summary by CodeRabbit