gabriel / musehub public
fix patch fix/184-issue-number-race-condition #1 / 1
AI Agent gabriel · 19 hours ago · Sep 11, 2026 · Diff

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.

sha256:a804f807c29b8a9bae6bbc2b4a72adbace37be1af83fae59b9f010b497719b24 sha
+11 ~1 symbols
3 changed · 1072 in snapshot files
sha256:93d4d35ab989045861257c3984817d64ceea4dae540bf999a694f208ea6723d8 snapshot
+11
symbols added
~1
symbol modified
3
files changed
1072
files in snapshot
0
dead code introduced
Semantic Changes 12 symbols
+ branch_labels variable variable branch_labels L37–37
+ depends_on variable variable depends_on L38–38
+ down_revision variable variable down_revision L36–36
+ downgrade function function downgrade L47–48
+ Sequence import import Sequence L29–29
+ Union import import Union L29–29
+ annotations import import annotations L27–27
+ op import import op L31–31
+ revision variable variable revision L35–35
+ upgrade function function upgrade L41–44
~ tests/test_musehub_issues.py .py 1 symbol added
+ test_concurrent_creates_never_produce_duplicate_issue_numbers function async_function test_concurrent_creates_never_produce_duplicate_issue_numbers L878–925
Files Changed
+1 ~2
1072 in snapshot
← Older Oldest on fix/184-issue-number-race-condition
All commits
Newer → Latest on fix/184-issue-number-race-condition

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:a804f807c29b8a9bae6bbc2b4a72adbace37be1af83fae59b9f010b497719b24 --body "your comment"