[glean-vnext] Fix intermittent re-auth from cross-process refresh-token rotation - #4
Open
pragati-agrawal-glean wants to merge 1 commit into
Open
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
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 (squashed) to the vendored source under
sources/glean-vnext/. Full commit history, review discussion, and E2E evidence live on that PR.Problem
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 sibling's in-memory copy is revoked. The sibling's next refresh gets
invalid_grant→ the SDK wipes the shared store →[SETUP_REQUIRED]— and the wipe destroys the winner's valid token, so every other session dies too.Fixes
tokens()re-reads the shared store on mtime advance (syncTokensFromDisk) so a sibling's rotated grant is used instead of a revoked in-memory copy.invalidateCredentials("tokens")adopts a newer on-disk token instead of wiping, with a short grace poll (GLEAN_ROTATION_GRACE_MS, default 2s) — the loser'sinvalid_grantusually arrives milliseconds before the winner's disk write.invalid_request(notinvalid_grant— observed live), which the SDK rethrows without touchinginvalidateCredentials; refresh-shaped connect failures now wait out the same grace window and retry once.Verification
E2E on an experimental QE pod (3-min token-age gate at validation; sign-in/refresh/rotation on real prod
/oauth), two plugin processes sharing one store:400→ store wiped →[SETUP_REQUIRED](bug reproduced on demand)Local checks here:
typecheck:bundle✓,test:bundle204/204 ✓,build:bundle✓. Note: local install of this repo's lockfile was blocked by the Socket npm firewall (undici@7.24.5403), so local runs used the sibling repo's node_modules — CI should be authoritative.🤖 Generated with Claude Code