Skip to content

fix(claude-sdk-oauth): resolve request auth from ambient Claude credentials - #836

Open
ismetanin wants to merge 1 commit into
code-yeongyu:mainfrom
ismetanin:fix/claude-sdk-oauth-ambient-auth
Open

fix(claude-sdk-oauth): resolve request auth from ambient Claude credentials#836
ismetanin wants to merge 1 commit into
code-yeongyu:mainfrom
ismetanin:fix/claude-sdk-oauth-ambient-auth

Conversation

@ismetanin

@ismetanin ismetanin commented Aug 12, 2026

Copy link
Copy Markdown

Problem

On a machine with a logged-in Claude CLI but no claude-sdk-oauth entry in auth.json, every request to the provider fails:

Error: Provider is not configured: claude-sdk-oauth
Runtime error (session_title_generation): Provider is not configured: claude-sdk-oauth

session_title_generation fires on session start, so the error appears before the user types anything. No configuration helps, because only a stored credential is ever consulted.

Reproduce with an empty ~/.senpi/auth.json, claude auth status exiting 0, and any claude-sdk-oauth model selected.

Root cause

A regression from 2acbb6e ("Require a real OAuth login for runtime availability"), which removed one line from the provider registration:

-		apiKey: "claude-sdk-oauth-managed",

That placeholder was the provider's only route to api-key auth. resolveProviderAuth() reads ambient credentials exclusively through provider.auth.apiKey.resolve():

// packages/ai/src/auth/resolve.ts
const stored = await readCredential(credentials, provider.id);
if (stored) { /* ... */ }
// Ambient (env vars, AWS profiles, ADC files).
return provider.auth.apiKey
	? resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, undefined)
	: undefined;   // <- oauth-only provider lands here

With the placeholder gone the provider registers oauth alone, and composeApiKeyAuth refuses to give it api-key auth:

// packages/coding-agent/src/core/provider-api-key-auth.ts
if (!inherited && rawKey === undefined && !headerSource && oauth) return undefined;

Availability still passed, so the models stayed selectable: Models.checkProviderAuth() falls back to oauth.check() with no credential, and that check accepts an env token or a logged-in CLI. Availability and resolution disagreed — the provider advertised models it could not authenticate.

The stream layer was never the problem: queryWithAuthLane has always had an ambient lane, and managedPool selects it by default (settings.tokenInjection ?? "ambient").

Fix

Give the extension a way to declare that it accepts ambient credentials, rather than reinstating a literal sentinel.

  • ExtensionOAuthConfig gains an additive optional resolveAmbient().
  • composeApiKeyAuth composes ambient-only api-key auth from it instead of returning undefined. It omits login, so the OAuth flow keeps ownership of login, and declines whenever a credential is passed, so a stored credential still wins.
  • createOAuthConfig implements resolveAmbient(), sharing a single ambientAvailable() predicate with check() so the two cannot drift apart again.

This does not restore the false availability 2acbb6e fixed. The removed literal reported configured unconditionally; resolveAmbient() is gated on the same real probe as check(), so a logged-out machine still resolves to undefined. The test added by that commit (test/suite/claude-sdk-oauth-extension.test.ts) passes unchanged.

availability.ts also memoises the ambient probe (30s TTL, shared in-flight read, rejections uncached). It spawns the Claude binary at ~200-650ms and now sits on the per-request auth path rather than only on catalog refresh.

changes.md entries added under src/core/ and the extension directory per the fork strategy.

Tests

test/claude-sdk-oauth-ambient-resolve.test.ts drives the real registration through composeModelProvider() and resolveProviderAuth() against an empty credential store — it fails on main with undefined and passes here:

  • resolves from an authenticated ambient CLI with nothing stored
  • resolves from CLAUDE_CODE_OAUTH_TOKEN with nothing stored
  • still reports not configured when logged out and nothing is stored

test/claude-sdk-oauth-ambient-cache.test.ts covers the probe cache: TTL reuse, re-probe after expiry, concurrent in-flight sharing, and no caching of rejections.

Verification

  • npm run check passes (biome, pinned-deps, ts-imports, shrinkwrap, install-lock, tsc, browser-smoke).
  • All 45 claude-sdk-oauth / auth-storage / provider / model-registry test files pass (435 tests).
  • Full packages/coding-agent suite shows no change against main: the same 14 files / 55 tests fail before and after, all from workspace dist not being built in this environment (ERR_MODULE_NOT_FOUND: pi-ai/dist/index.js).

🤖 Generated with Claude Code


Summary by cubic

Fixes failed requests for claude-sdk-oauth when the Claude CLI is logged in but no credential is stored. Requests now use ambient credentials (env token or CLI) and no longer error on session start.

  • Bug Fixes
    • Added resolveAmbient() to the OAuth config and updated composeApiKeyAuth to build ambient-only API key auth when the provider supplies it. Stored credentials still win; login stays on OAuth.
    • Unified availability and resolution behind one ambient predicate to prevent “Provider is not configured: claude-sdk-oauth”.
    • Memoized the ambient probe (30s TTL, shared in-flight, errors not cached) to avoid repeated CLI spawns.
    • Added tests for ambient resolution (env token and CLI) and the probe cache.

Written for commit 17fa0ec. Summary will update on new commits.

Review in cubic

…ntials

The provider registers only `oauth`, so `composeApiKeyAuth` gives it no
api-key auth. `resolveProviderAuth` reads ambient credentials exclusively
through `apiKey.resolve()`, so with nothing in auth.json it returns
undefined and every request fails with:

    Provider is not configured: claude-sdk-oauth

Availability disagreed with resolution: `check()` accepts an environment
token or a logged-in Claude CLI, so the models are offered and selected —
including for session_title_generation, which fires on session start.
Users with a working `claude` login hit the error before typing anything,
and no amount of configuration helped, since only a stored credential was
ever consulted. The stream layer already supports this: `queryWithAuthLane`
has an "ambient" lane and `managedPool` defaults to it.

Let an extension OAuth config expose `resolveAmbient`. When a provider has
no key, headers or base but supplies one, compose ambient-only api-key auth
from it — no `login`, so the OAuth flow still owns login, and a stored
credential still wins. In claude-sdk-oauth this shares one predicate with
`check()`, so availability and resolution can no longer disagree.

Memoise the ambient probe (30s TTL, shared in-flight, errors uncached): it
spawns the Claude binary at ~200-650ms and now sits on the per-request path.
@ismetanin
ismetanin force-pushed the fix/claude-sdk-oauth-ambient-auth branch from db436e9 to 17fa0ec Compare August 12, 2026 10:12
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