Fix two production crashes and bump to v3.2.0

1. Fix IndexError in current_player() when player leaves mid-game
   - remove_player() now adjusts current_player_index after popping
   - current_player() has safety bounds check as defensive fallback

2. Fix AssertionError in StaticFiles catching WebSocket upgrades
   - Wrap static file mount to reject non-HTTP requests gracefully
   - Starlette's StaticFiles asserts scope["type"] == "http"

Both crashes were observed in production on 2026-02-28 during a
multi-player session. The IndexError cascaded into reconnection
attempts that hit the StaticFiles assertion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-03-05 20:30:08 -05:00
parent 7f0f580631
commit a8b521f7f7
2 changed files with 26 additions and 5 deletions

View File

@@ -782,9 +782,17 @@ class Game:
for i, player in enumerate(self.players):
if player.id == player_id:
removed = self.players.pop(i)
# Adjust dealer_idx if needed after removal
if self.players and self.dealer_idx >= len(self.players):
self.dealer_idx = 0
if self.players:
# Adjust dealer_idx if needed after removal
if self.dealer_idx >= len(self.players):
self.dealer_idx = 0
# Adjust current_player_index after removal
if i < self.current_player_index:
# Removed player was before current: shift back
self.current_player_index -= 1
elif self.current_player_index >= len(self.players):
# Removed player was at/after current and index is now OOB
self.current_player_index = 0
self._emit("player_left", player_id=player_id, reason=reason)
return removed
return None
@@ -807,6 +815,8 @@ class Game:
def current_player(self) -> Optional[Player]:
"""Get the player whose turn it currently is."""
if self.players:
if self.current_player_index >= len(self.players):
self.current_player_index = self.current_player_index % len(self.players)
return self.players[self.current_player_index]
return None