Skip to content

fix(auth): name the user a re-check blanked when its 401 ends the session - #1948

Merged
dawsontoth merged 2 commits into
stagefrom
claude/cross-tab-sign-out-followup
Oct 13, 2026
Merged

dawsontoth merged 2 commits into
stagefrom
claude/cross-tab-sign-out-followup

Conversation

@dawsontoth

@dawsontoth dawsontoth commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Problem

This follows up #1936, which is merged and deployed to stage. #1936 makes each tab follow a sign-out by writing an announcement, Studio:AppSignOut, that names the user whose session ended.

The last cross-model round on #1936 finished after the merge. It found sign-outs that named the wrong user, or no one:

  • A re-check that gets a 401 names nobody. A tab's own reloadUser blanks the app user before it asks central manager. When that check came back 401, signOutAllLocally had no user to name. Other tabs ignored the announcement and kept polling until their next 403 toasted, which is the bug fix(auth): sign out every tab when one tab signs out #1936 fixes.
  • A failed /Logout/ leaves a stale user. The sweep remembered only the user it cleared. If /Logout/ failed and the tab then signed in as someone else, a later 401 named the old user. Tabs signed in as the new user never heard.
  • No-user announcements. A sign-out with no one to name still wrote an announcement, which woke every other tab for nothing.
  • Mounted assertions ran too early. Some no-toast and no-request assertions ran before the redirect.

Solution

Verification

Mutation checks

I reverted each guard locally and confirmed that a test goes red:

mutation fails
name only the current user, not the held one 3 mounted cases and 2 unit tests
keep the first held user instead of the latest the failed-/Logout/ unit test
a follower keeps the held user the follower unit test
announce when there is no user 2 unit tests
return any id/username value, not only a non-empty string the 3 no-usable-identity unit tests

The only other change is a removed narrating comment in the handler test.

Cross-model review

Round 1 (20115cfa6). Reviewers: codex gpt-5.6-sol (graded), gemini and Harper domain adjudication; independent=true. The Cursor lens timed out. Verdict: LGTM, adjudicated severity nit.

  • Nit, fixed: the invariant comment moved from the call site to the heldAppUser field, now 5fb07be22.
  • Gemini major, dropped as factually wrong: it claimed the failed-/Logout/ test had changed. It hadn't; that hunk edits the next test.
  • Gemini minor, dropped as factually wrong: it claimed a late 200 refills the held user after a sign-out. The '*' generation bump makes loadUser return first.

gemini-code-assist on this PR (one thread): userIdentity could return undefined. I took it and widened it to non-string and empty ids, fixed in 70412063e. I replied on the thread, pushed, then resolved it.

Round 2 (84d302940, the gemini fix). Reviewers: codex, gemini, cursor-muse and Harper domain adjudication; independent=true. Verdict: LGTM, adjudicated severity nit. The nit was that the field comment and DESIGN.md wording were imprecise (local Studio, the follower clear, and an undefined "U"). I fixed it in a comment-only amend; that amend and a rebase with no conflicts make the head 70412063e.

#1946 tracks the pre-existing finding from #1936's last round: localStorage.clear() erases the explorer-epoch bump.

🤖 Generated with Claude Code

Related PRs: #1936 overlaps (merged; this PR fixes what its last review round found after the merge)

Review-Coverage: authored=claude; ran=codex,gemini,cursor-muse; adjudicated=domain; blocked=cursor-grok(failed),cursor-composer(out-of-budget); declined=cursor-kimi,muse; rounds=2; full=1 @ 7041206

Review-Attention: study ~10m (sensitive: authStore.ts; decisions: capture-on-every-write, name-not-session-matching) @ 7041206

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the authentication store to track the last confirmed app user (heldAppUser) through session checks and sweeps, ensuring correct user information is announced upon sign-out. It introduces a userIdentity helper function and updates associated tests to cover these scenarios. The feedback suggests adding a fallback to the userIdentity helper to prevent returning undefined at runtime if user.username is missing, which could otherwise bypass null checks and cause unexpected serialization in localStorage.

Comment thread src/features/auth/store/authStore.ts Resolved
@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 77.86% 13781 / 17699
🔵 Statements 77.82% 14632 / 18801
🔵 Functions 72.97% 3635 / 4981
🔵 Branches 72.99% 10869 / 14891
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/features/auth/store/authStore.ts 91.31% 82.93% 91.25% 91.77% 117, 203-214, 219-222, 270, 291, 312-323, 331, 336-339, 441-444, 803, 808, 885, 909
Generated in workflow #2341 for commit 7041206 by the Vitest Coverage Report Action

@dawsontoth
dawsontoth marked this pull request as ready for review October 13, 2026 18:01
@dawsontoth
dawsontoth requested a review from a team as a code owner October 13, 2026 18:01
dawsontoth and others added 2 commits October 13, 2026 14:18
…sion

A tab's own `reloadUser` blanks the app user before asking central
manager, so when that check came back 401 the announcement named nobody
and the other tabs kept polling until their 403. The same happened to
the sweep's user once a failed `/Logout/` was followed by a sign-in as
someone else, because the remembered user was only the swept one.

The store now keeps the last app user it held until a sign-out
announces them, or a follower drops them, and announces nothing when it
has no one to name. A page load whose first check 401s still names no
one: it never knew its user.

The mounted test's no-request and no-toast assertions now run after the
redirect and a further poll, and two narrating comments are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`userIdentity` returned whatever the user's `id` or `username` held. A
user record with neither, or with a non-string or empty id, therefore
produced `undefined` or a number. That slipped past the announcement's
null check and was written as an announcement naming nobody, and a tab
holding such a user would follow any announcement that also named
nobody. It now returns a non-empty string or null.

Raised by gemini-code-assist on #1948.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dawsontoth
dawsontoth force-pushed the claude/cross-tab-sign-out-followup branch from 5fb07be to 7041206 Compare October 13, 2026 18:19
@dawsontoth
dawsontoth merged commit 13f125f into stage Oct 13, 2026
3 checks passed
@dawsontoth
dawsontoth deleted the claude/cross-tab-sign-out-followup branch October 13, 2026 20:38
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