Background
AuthProvider (frontend/src/auth/AuthContext.tsx) fires a refresh() on mount.
If the operator signs in while that first fetchAuthStatus is still in flight, the
response — requested before the credential existed, so it carries user: null —
can land after login() has already written the authenticated user, and overwrite
it. The shell drops back to the login screen despite a successful sign-in; the next
refresh() (or a re-login) recovers it.
This is the mirror image of the P3-4 race fixed in #82. P3-4 closed the
dangerous direction — a late refresh restoring a session that a 401/logout had
already ended. This one runs the other way: a late refresh clearing a session
that just legitimately started.
Why it is a small standalone follow-up, not part of #82
Scope of work
- Decide the sequencing rule for authentication vs. an in-flight
refresh() and
apply it in login, verifyMfa, and setup (one mechanism, matching the guard
idiom already in the file — not a third pattern).
- Extend
frontend/src/auth/AuthContext.test.tsx with a jsdom case: hold the mount
refresh open, complete a login, then resolve the stale status with user: null
and assert the session stays signed in. Confirm it fails before the fix.
- Confirm the fix does not weaken the P3-4 invariant — a logged-out session must
still never be restored by a late-arriving refresh — and that the existing race
tests still pass.
Acceptance
Ref: frontend review P3-4 (docs/reviews/frontend-review.md § Priority 3) and the
2026-07-24 § 14 entry added in #82.
Background
AuthProvider(frontend/src/auth/AuthContext.tsx) fires arefresh()on mount.If the operator signs in while that first
fetchAuthStatusis still in flight, theresponse — requested before the credential existed, so it carries
user: null—can land after
login()has already written the authenticated user, and overwriteit. The shell drops back to the login screen despite a successful sign-in; the next
refresh()(or a re-login) recovers it.This is the mirror image of the P3-4 race fixed in #82. P3-4 closed the
dangerous direction — a late refresh restoring a session that a 401/logout had
already ended. This one runs the other way: a late refresh clearing a session
that just legitimately started.
Why it is a small standalone follow-up, not part of #82
login()never interacted with thein-flight mount refresh before that PR either, and fix(frontend): sequence auth refresh against session invalidation (P3-4) #82 deliberately left that
path untouched to keep its scope to the invalidation invariant.
in: no session is granted that the backend hasn't authorized, so there is no
security edge — it is a UX/correctness wart.
refresh(), restores thecorrect state.
sessionGenerationref and gaverefresh()acreateLatestGuard()token (frontend/src/lib/latest.ts), so thefix is most likely a couple of lines: have the successful-authentication paths
(
login,verifyMfa,setup) supersede any in-flight refresh — e.g. burn aguard token — so an older status response returns without writing.
Scope of work
refresh()andapply it in
login,verifyMfa, andsetup(one mechanism, matching the guardidiom already in the file — not a third pattern).
frontend/src/auth/AuthContext.test.tsxwith a jsdom case: hold the mountrefresh open, complete a login, then resolve the stale status with
user: nulland assert the session stays signed in. Confirm it fails before the fix.
still never be restored by a late-arriving refresh — and that the existing race
tests still pass.
Acceptance
fetchAuthStatusanswered before login and resolving after it can nolonger clear the authenticated user.
logout()mid-refresh) stillpass unchanged.
docs/ARCHIVE.md§ 14 entry.Ref: frontend review P3-4 (
docs/reviews/frontend-review.md§ Priority 3) and the2026-07-24 § 14 entry added in #82.