[glean-vnext] Fix intermittent re-auth from cross-process refresh-token rotation - #4
Open
pragati-agrawal-glean wants to merge 5 commits into
Open
[glean-vnext] Fix intermittent re-auth from cross-process refresh-token rotation#4pragati-agrawal-glean wants to merge 5 commits into
pragati-agrawal-glean wants to merge 5 commits into
Conversation
pragati-agrawal-glean
requested review from
eshwar-sundar-glean,
mohit-gupta-glean and
swarup-padhi-glean
July 30, 2026 07:20
…en rotation Port of gleanwork/glean-plugins-vnext#44 (squashed; full history and E2E evidence there). Each host session runs its own plugin process sharing one credentials file. The Glean OAuth server rotates refresh tokens on every refresh with no grace period, so when one session refreshes, every other session's in-memory copy is revoked; their next refresh gets invalid_grant, the SDK wipes the SHARED store, and the user sees [SETUP_REQUIRED] — plus every other live session dies with them. Fixes (E2E-verified on an experimental pod against real prod /oauth — bug reproduced on demand with the old build, silent recovery in both race shapes with this change): - tokens()/syncTokensFromDisk: mtime-guarded re-read of the shared store so a sibling's rotated grant is picked up before the SDK refreshes. - invalidateCredentials('tokens'): adopt a newer on-disk token instead of wiping — with a grace-window poll (GLEAN_ROTATION_GRACE_MS, 2s) because the loser's invalid_grant usually lands milliseconds before the winner's write. - Connect-level sibling-refresh retry: concurrent refreshes of the same grant make fosite fail the loser with invalid_request (not invalid_grant — observed live), which the SDK rethrows raw; recognize refresh-shaped failures, wait out the grace window, retry once. - saveCredentials: temp-file + rename so concurrent writers can't leave a torn store that parses as wiped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pragati-agrawal-glean
force-pushed
the
pragati/fix-plugin-token-rotation-reauth
branch
from
July 30, 2026 10:49
c640b64 to
10b5eaa
Compare
david-hamilton-glean
approved these changes
Aug 20, 2026
Comment on lines
+263
to
+268
| // Match broadly; the caller's disk re-check gates the actual retry. | ||
| function isLikelyRefreshFailure(error: unknown): boolean { | ||
| const msg = error instanceof Error ? error.message : String(error); | ||
| return /refresh|invalid_grant|invalid_request|oauth/i.test(msg); | ||
| } | ||
|
|
Member
There was a problem hiding this comment.
3/5 (strong opinion: non-blocking)
Do we actually need to use heuristics like this?
We should be getting back responses like
{
"error": "invalid_grant",
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client."
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Port of gleanwork/glean-plugins-vnext#44 to the vendored source under
shared/glean/mcp/.Problem
Each host session runs its own plugin process while sharing one credential file. The Glean OAuth server rotates refresh tokens on every refresh and invalidates the presented refresh token. When one process refreshes, a sibling with a stale in-memory grant can receive
invalid_grantorinvalid_requestand incorrectly trigger re-authentication for the shared store.Fix
tokens()reloads credentials from disk on every access throughsyncTokensFromDisk(). Correctness no longer depends on file mtime, and there is no mtime-based synchronization or environment override.invalidateCredentials("tokens")waits for a sibling's changed access token for a fixed two-second grace period, polling every 100 ms. It adopts a sibling's new grant when present and clears tokens only when no sibling refresh appears.createRemoteClient()retries once when a newer access token appears on disk. It also handles structured OAuth errors witherrorCodeinvalid_requestorinvalid_grant, instead of matching human-readable error messages.chmodSync(tmpPath, FILE_MODE)applied before rename so temporary files are also0600.Agent-plugins-specific behavior preserved
withConnectLockconnection serialization.shared/glean/mcp/; this PR does not introduce a separatesources/glean-vnext/tree.Validation
The live two-process refresh-race verification was performed against the equivalent vNext candidate using the non-production SST endpoint and a two-minute access-token lifetime. The agent-plugins port is covered by its bundle typecheck, test, and build validation; no raw credentials are included here.
— sent via Glean Desktop