Fix tournament blockers that can crash or hang bracket progression
6 verified bugs in the tournament SQL layer that can cause matches to not start, teams to get
double-assigned, or tournaments to hang indefinitely.
Bugs
1. Swiss pool double-assignment (Critical)
assign_teams_to_swiss_pools evaluates get_swiss_team_pools() once at loop start. Teams borrowed from
adjacent pools remain in the original pool's stale team_ids, causing one team to be assigned to two
matches in the same round.
- File:
api/hasura/functions/tournaments/assign_teams_to_swiss_pools.sql
2. Bracket slot overwrite on concurrent writes (High)
The fallback path in assign_team_to_bracket_slot uses a stale read and has no IS NULL guard on the
UPDATE. A concurrent bracket assignment can overwrite an existing team, removing it from the tournament.
- File:
api/hasura/functions/tournaments/assign_team_to_bracket_slot.sql
3. Non-deterministic stage seeding on tied rankings (High)
seed_stage uses OFFSET on v_team_stage_results. When teams have identical stats across all
tiebreakers, PostgreSQL returns them in arbitrary order. Re-running after pause/resume can swap teams
between bracket slots.
- File:
api/hasura/views/v_team_stage_results.sql
4. Silent scheduling failures hang tournaments (Medium)
schedule_tournament_match has 5 RETURN NULL paths with no logging. If any fires unexpectedly, the
bracket has teams but no match, and the tournament hangs with zero diagnostic output.
- File:
api/hasura/functions/tournaments/schedule_tournament_match.sql
5. Auto-forfeit awards win to wrong team (Medium)
CancelExpiredMatches.forfeitMatch() uses lineup_1.is_ready ? lineup_1 : lineup_2, silently forfeiting
to lineup_2 when neither team is ready.
- File:
api/src/matches/jobs/CancelExpiredMatches.ts
6. Orphaned brackets prevent tournament completion (Medium)
Brackets can get stuck with bye=false, finished=false, no teams, and no pending feeders.
check_tournament_finished counts them as unfinished, so the tournament never reaches Finished status.
- File:
api/hasura/functions/tournaments/check_tournament_finished.sql
Fix tournament blockers that can crash or hang bracket progression
6 verified bugs in the tournament SQL layer that can cause matches to not start, teams to get
double-assigned, or tournaments to hang indefinitely.
Bugs
1. Swiss pool double-assignment (Critical)
assign_teams_to_swiss_poolsevaluatesget_swiss_team_pools()once at loop start. Teams borrowed fromadjacent pools remain in the original pool's stale
team_ids, causing one team to be assigned to twomatches in the same round.
api/hasura/functions/tournaments/assign_teams_to_swiss_pools.sql2. Bracket slot overwrite on concurrent writes (High)
The fallback path in
assign_team_to_bracket_slotuses a stale read and has noIS NULLguard on theUPDATE. A concurrent bracket assignment can overwrite an existing team, removing it from the tournament.
api/hasura/functions/tournaments/assign_team_to_bracket_slot.sql3. Non-deterministic stage seeding on tied rankings (High)
seed_stageusesOFFSETonv_team_stage_results. When teams have identical stats across alltiebreakers, PostgreSQL returns them in arbitrary order. Re-running after pause/resume can swap teams
between bracket slots.
api/hasura/views/v_team_stage_results.sql4. Silent scheduling failures hang tournaments (Medium)
schedule_tournament_matchhas 5RETURN NULLpaths with no logging. If any fires unexpectedly, thebracket has teams but no match, and the tournament hangs with zero diagnostic output.
api/hasura/functions/tournaments/schedule_tournament_match.sql5. Auto-forfeit awards win to wrong team (Medium)
CancelExpiredMatches.forfeitMatch()useslineup_1.is_ready ? lineup_1 : lineup_2, silently forfeitingto lineup_2 when neither team is ready.
api/src/matches/jobs/CancelExpiredMatches.ts6. Orphaned brackets prevent tournament completion (Medium)
Brackets can get stuck with
bye=false,finished=false, no teams, and no pending feeders.check_tournament_finishedcounts them as unfinished, so the tournament never reachesFinishedstatus.api/hasura/functions/tournaments/check_tournament_finished.sql