@@ -108,20 +108,36 @@ async def ensure_user_row(session: AsyncSession, user: User) -> UserRow:
108108 except IntegrityError :
109109 # The collision could be on any of the three unique constraints:
110110 # (oauth_provider, oauth_subject), email_lookup_hash, or id (PK).
111- # Only the FIRST means we hit our own identity (same logical
112- # user via concurrent insert by a peer process); the latter two
113- # mean a *different* identity already owns that id or email.
114111 await session .rollback ()
112+
113+ # Case 1: (oauth_provider, oauth_subject) race — a peer process beat us
114+ # to the INSERT for this exact identity.
115115 existing = await user_repo .get_by_oauth (provider , subject )
116116 if existing :
117117 await user_repo .update_last_login (existing .id )
118118 return existing
119- # PK collision across providers (different subject chose the
120- # same id). Retry with a UUID id; (provider, subject) and email
121- # differ from the conflicting row so this insert succeeds.
122- # email_lookup_hash collision is NOT recoverable here — that's
123- # a real conflict (two identities with the same email) and we
124- # let it propagate to the caller.
119+
120+ # Case 2: email_lookup_hash race — same email already inserted (most
121+ # common for the synthetic anonymous@localhost user in no-auth mode when
122+ # two concurrent requests both observe an empty table). Look up by email;
123+ # if the row's (provider, subject) matches ours it's a concurrent insert
124+ # of the same identity and we can safely return it.
125+ if user .email :
126+ by_email = await user_repo .get_by_email (user .email )
127+ if (
128+ by_email
129+ and by_email .oauth_provider == provider
130+ and by_email .oauth_subject == subject
131+ ):
132+ await user_repo .update_last_login (by_email .id )
133+ return by_email
134+
135+ # Case 3: PK collision across providers (a different identity already
136+ # occupies id=subject). Retry with a UUID; (provider, subject) differs
137+ # from the conflicting row so the INSERT will succeed.
138+ # If the collision was on email_lookup_hash for a genuinely different
139+ # identity (two real users sharing an email) this insert also fails —
140+ # that propagates to the caller as intended.
125141 new_id = str (uuid .uuid4 ())
126142 row = UserRow (
127143 id = new_id ,
0 commit comments