Problem
TestRefresh_ConcurrentRotation_OneWinner (backend/internal/auth/token_test.go) fails
about one run in five, in isolation, with -count=1:
token_test.go:208: Error: Not equal: expected: 0, actual: 1
Messages: concurrent reuse revokes the family
The flake is real and it points at the code, not at the test.
Why
TokenManager.RefreshTokenPair (backend/internal/auth/token.go) does, in order:
- claim the row atomically (
UPDATE … WHERE id = ? AND rotated_at IS NULL);
- resolve the session scope;
GenerateTokenPair, which inserts a new refresh row in the same family.
A concurrent loser takes the RowsAffected != 1 branch and calls
revokeFamily(familyID) immediately. Nothing orders these two: when the loser's
DELETE … WHERE family_id = ? runs before the winner's INSERT, the winner's
brand-new refresh token is written after the family was revoked and survives.
So a token-reuse event — the signal that a refresh token leaked — can leave a live
session behind, which is exactly what family revocation exists to prevent. The
window is small, but it is the window an attacker replaying a stolen token races.
Acceptance criteria
- Given N concurrent rotations of one refresh token, when exactly one wins and at least one is flagged as reuse, then no refresh row of that family survives — asserted by the existing test, run with
-count=50 -race and green every time.
- The winner's new token is not written after a revocation of its family (for instance: claim + insert in one transaction, and revoke by family in the same serialized path; or re-check the family after insert and delete).
- No behaviour change for the ordinary, uncontended refresh path;
internal/auth tests stay green.
Definition of Done
go test ./internal/auth/ -run TestRefresh -count=50 -race pasted in the issue comment.
- The ordering argument written down in the code, next to the claim.
Context
Found while verifying #718/#719/#720 in the running app: the full go test ./... failed
here once, and the test then failed 1 run in 5 in isolation on an untouched checkout.
This is not caused by those branches; none of them touch internal/auth.
Problem
TestRefresh_ConcurrentRotation_OneWinner(backend/internal/auth/token_test.go) failsabout one run in five, in isolation, with
-count=1:The flake is real and it points at the code, not at the test.
Why
TokenManager.RefreshTokenPair(backend/internal/auth/token.go) does, in order:UPDATE … WHERE id = ? AND rotated_at IS NULL);GenerateTokenPair, which inserts a new refresh row in the same family.A concurrent loser takes the
RowsAffected != 1branch and callsrevokeFamily(familyID)immediately. Nothing orders these two: when the loser'sDELETE … WHERE family_id = ?runs before the winner'sINSERT, the winner'sbrand-new refresh token is written after the family was revoked and survives.
So a token-reuse event — the signal that a refresh token leaked — can leave a live
session behind, which is exactly what family revocation exists to prevent. The
window is small, but it is the window an attacker replaying a stolen token races.
Acceptance criteria
-count=50 -raceand green every time.internal/authtests stay green.Definition of Done
go test ./internal/auth/ -run TestRefresh -count=50 -racepasted in the issue comment.Context
Found while verifying #718/#719/#720 in the running app: the full
go test ./...failedhere once, and the test then failed 1 run in 5 in isolation on an untouched checkout.
This is not caused by those branches; none of them touch
internal/auth.