Conversation
…existing account `upgradeAnonymousUser` sets `users.email`, so it hits the `users_email_key` unique constraint whenever the email someone authenticates with already has an account -- i.e. every time a returning user browses anonymously and then logs in. Production logged a P2002 stack trace for each one. `serializeUser` already handled this by catching everything and falling back to `findOrCreateUser`, which is the right outcome but hid genuine failures behind the same `console.log`. Make the no-op cases part of the contract instead: `upgradeAnonymousUser` returns `null` for P2002 (email taken) and P2025 (not anonymous, or no such user) and rethrows anything else, so a real database failure now fails the login rather than passing silently. `parseFromAnonymous` takes over the one thing the blanket catch was legitimately guarding -- `toUUID` throwing on a malformed `fromAnonymous` value -- and folds in the magic-link `" "` placeholder check. The Google branch now runs `updateUser` only when the upgrade actually happened; previously a failure there discarded the successful upgrade. Anonymous work is still not merged into the existing account, so a student who takes an assignment anonymously and then logs in remains two rows in the instructor's list. That is a separate decision. Tests move to `auth/anonymousUpgrade.test.ts` and pin all five outcomes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6R3jTUpCKjeAKV64cd6FC
cqnykamp
marked this pull request as draft
September 1, 2026 14:37
Contributor
Author
|
/deploy-dev |
|
✅ Deployed dev3 is shared: the next push to |
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.
The error
Production logs a P2002 stack trace, several times a day:
upgradeAnonymousUsersetsusers.email, which is unique. So it fails wheneverthe email someone authenticates with already belongs to an account — that is,
every time a returning user browses anonymously and then logs in. It is an
ordinary login, not a failure.
serializeUseralready produced the right outcome (catch, fall back tofindOrCreateUser, log them in to their real account), but the catch swallowedevery other error the same way, so a genuine database failure was
indistinguishable from this in the logs.
The change
upgradeAnonymousUserreturnsnullfor the two no-op cases — P2002 (emailalready taken) and P2025 (account is not anonymous, or does not exist) — and
rethrows everything else. The caller's existing
if (!u)fallback coversboth.
serializeUserbranches drop the blankettry/catch+console.log.A real database failure now reaches
asyncPassport→done(err)and failsthe login instead of passing silently. This is the one behavior change worth
watching after deploy.
auth/parseFromAnonymoustakes over the thing the blanket catch waslegitimately guarding:
toUUIDthrows on a malformedfromAnonymousvalue,and a bad cookie must not fail a login. It also folds in the magic-link
" "placeholder check that was inline.
updateUseronly when the upgrade actuallyhappened. Previously it sat inside the same
try, so a failure therediscarded an otherwise successful upgrade and hid the error.
Not in scope
Anonymous work is still not merged into the existing account. A student who
takes an assignment anonymously and then logs in stays two rows in
assignmentScores, so they appear twice in the instructor's student list(
apps/api/src/query/assign.ts:908lists every row, with no anonymity filter).Merging is a product decision about gradebook data and deserves its own change.
Tests
The single happy-path test moves out of
test/users.test.tsintoauth/anonymousUpgrade.test.tsand grows to pin all five outcomes: unused email(same
userId, so the session survives), email taken →null, email taken →both accounts untouched, account not anonymous →
nulland email unchanged,user missing →
null. Plus 3 unit tests forparseFromAnonymous.Full API suite: 32 files, 415 passed.
🤖 Generated with Claude Code
https://claude.ai/code/session_01T6R3jTUpCKjeAKV64cd6FC