Skip to content

feat(model-client): reuse one OAuth login across branch-specific token URLs#2774

Open
abstraktor wants to merge 1 commit into
mainfrom
017-hybrid-modelix-tokens
Open

feat(model-client): reuse one OAuth login across branch-specific token URLs#2774
abstraktor wants to merge 1 commit into
mainfrom
017-hybrid-modelix-tokens

Conversation

@abstraktor

Copy link
Copy Markdown
Collaborator

Problem

On the JVM, ModelixAuthClient caches OAuth credentials per ModelClientV2 instance and keys the cache by the full token URL. When a deployment issues branch-specific token URLs (same issuer + client ID, but a different per-branch token endpoint), each branch forks the credential cache and triggers a separate interactive PKCE login. Opening several repositories/branches in one session therefore prompts the user to log in repeatedly.

Change

  • Key the credential cache by (authorizationUrl, clientId) — drop tokenUrl from TokenCacheKey. The per-branch token URL still selects which token is minted, but it no longer forks the refresh-credential cache.
  • Move the credential cache to a companion object so it is shared across ModelixAuthClient instances within a process: one interactive login per session. Separate processes still log in independently (in-memory only, nothing persisted).
  • Store the raw refresh token and perform a refresh_token grant against each branch's token URL, so every branch obtains its own scoped access token from the single login.
  • Single-flight the refresh/authorize path with a mutex so concurrent branch opens don't race on the refresh token.
  • On invalid_grant, discard the stored refresh token and fall back to interactive PKCE.

Tests

model-client/src/jvmTest/.../client2/CredentialCachingTest.kt:

  • cache key is equal for two configs that share issuer + client ID but differ only in token URL;
  • two ModelClientV2 clients on different branch token URLs require exactly one PKCE login (the second reuses the refresh token);
  • an invalid_grant refresh response triggers a fresh PKCE login.

Existing RefreshTokenTest, OAuthTest, TokenEndpointTest, and ModelixAuthClientTest continue to pass.

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Test Results

  259 files    259 suites   46m 36s ⏱️
1 463 tests 1 452 ✅ 11 💤 0 ❌
1 473 runs  1 462 ✅ 11 💤 0 ❌

Results for commit 9769206.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

JVM coverage report

Overall Project 57.49% -0.06%
Files changed 83.31% 🍏

File Coverage
ModelixAuthClient.kt 83.9% -8.63% 🍏
OAuthConfig.kt 63.78% 🍏

@abstraktor abstraktor force-pushed the 017-hybrid-modelix-tokens branch 3 times, most recently from b3031af to 1f6d57a Compare June 29, 2026 07:34
…30–T033)

- Remove `tokenUrl` from `TokenCacheKey` so all branch-specific token
  endpoints keyed to the same issuer+clientId share one credential entry.
- Move `cachedTokens` to a `ModelixAuthClient` companion object so the shared
  refresh token is visible across all `ModelixAuthClient` instances (one per
  `ModelClientV2` builder) — one interactive login per process; separate
  processes log in independently (credentials in memory only).
- Store the raw refresh token; each branch sends its own
  `grant_type=refresh_token` to its own tokenUrl, obtaining a branch-scoped
  access token without a second PKCE login. Cache those access tokens per
  token URL and reuse them until a 60s pre-expiry skew margin to avoid
  re-minting (and re-issue reactively on a 401).
- Single-flight all refresh/PKCE per (issuer, clientId) via `authMutex`,
  always chaining to the latest rotated refresh token.
- On `invalid_grant`, discard the stored refresh token and the access tokens
  minted from it, then fall back to interactive PKCE.
- Replace the removed `getTokens()` with Java-friendly, non-suspending
  accessors `getRefreshToken(config)` / `getAccessToken(config)` that read the
  process-wide cache; make the suspend auth helpers internal.
- Stop logging the access token; inject the IO dispatcher, extract port
  constants, and use error() so detekt stays clean.
- Tests (`CredentialCachingTest`): cache-key (T030), two-branch one-PKCE
  (T032), invalid_grant fallback (T033), plus concurrent single-flight,
  single-use refresh-token rotation, and cross-client-isolation. Tests are
  isolated by unique per-test cache keys (random ports), not by clearing the
  shared cache — a global clear would race with parallel tests (e.g. OAuthTest).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@abstraktor abstraktor force-pushed the 017-hybrid-modelix-tokens branch from 1f6d57a to 9769206 Compare June 29, 2026 08:02
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