fix: musehub#184 — concurrent issue creates raced, producing duplicate numbers
_next_issue_number() computed the next per-repo issue number via SELECT MAX(number) then INSERT max+1, with no locking and no unique constraint. Reproduced directly, twice:
- Against real staging: 10 concurrent `muse hub issue create` calls against the same repo produced 3 rows at #199 and 2 rows at #197 (all persisted — nothing was silently lost, the numbers just collided). Test data cleaned up afterward; confirmed zero duplicates remained across the whole database before applying the migration below. - Locally, with the fix reverted to confirm the regression test is real: all 10 concurrent creates collided on #1 (an even more direct hit than staging's timing happened to produce).
This does NOT fully explain the original #184 incident (a single row silently vanishing, no duplicate ever appearing) — that remains a separate, still-open mystery noted in the ticket. But it's a real, independently confirmed, critical data-integrity bug in its own right, now fixed regardless of whether it's also the original bug's cause.
Fix, belt and suspenders: - musehub/services/musehub_issues.py::_next_issue_number now locks the repo row (SELECT ... FOR UPDATE) before computing MAX(number), serializing concurrent allocation for the same repo within the same transaction that later inserts and commits. - alembic/versions/0076: UNIQUE constraint on musehub_issues (repo_id, number) as a backstop, so any future code path that bypasses the lock fails loudly (IntegrityError) instead of silently producing a duplicate. Verified staging has zero existing duplicates before this was written (required for the migration to apply cleanly).
tests/test_musehub_issues.py::test_concurrent_creates_never_produce_duplicate_issue_numbers — fires 10 real concurrent creates via the test HTTP client against a real Postgres-backed test DB (not mocked), asserts all 10 numbers are distinct (1-10), asserts zero duplicate (repo_id, number) rows exist via direct DB query, and asserts all 10 rows actually persisted (no data loss either). Confirmed this test fails without the fix (all 10 collide on #1) and passes with it — genuine red/green, not a tautology.
Semantic Changes
12 symbols
Files Changed
+1
~2
1072 in snapshot
0 comments
muse hub commit comment sha256:a804f807c29b8a9bae6bbc2b4a72adbace37be1af83fae59b9f010b497719b24 --body "your comment"
No comments yet. Be the first to start the discussion.