HOTFIX: Production bot failed to start due to circular import.
Root cause: utilities/dropdown.py importing from command_logic/logic_gameplay.py
while logic_gameplay.py imports from utilities/dropdown.py.
Solution: Created play_lock.py as standalone module containing:
- release_play_lock()
- safe_play_lock()
Both modules now import from play_lock.py instead of each other.
Error message:
ImportError: cannot import name 'release_play_lock' from partially
initialized module 'command_logic.logic_gameplay' (most likely due
to a circular import)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
CRITICAL BUG FIX: Play locks were never released on exceptions, causing
permanent user lockouts. Found 13 stuck plays in production.
Changes:
1. Added lock_play parameter to checks_log_interaction() (default True)
2. Removed unnecessary locks from read-only commands:
- /settings-ingame (game settings, not play state)
- /show-card defense (read-only display)
- /substitute commands (just show UI, lock in callback)
3. Added safe_play_lock() context manager for automatic lock release
4. Added play locking to substitution callbacks:
- SelectBatterSub.callback()
- SelectReliefPitcher.callback()
5. Global error handler now releases stuck locks automatically
Architecture:
- Commands that display UI or read data: No lock
- Commands that modify play state: Lock at last possible moment
- 3-layer defense: manual release, context manager, global handler
Resolves race condition from concurrent play modifications.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Bug: When a user attempted to substitute a player who didn't have the required
position rating, the bot would display an error message but leave the database
session in an inconsistent state. The old player was marked inactive and flushed
to the session, but when the position check failed, the function returned early
without rolling back the session. This left the session dirty, causing crashes
on subsequent operations.
Fix: Added session.rollback() before returning when PositionNotFoundException
is caught, ensuring the database session is cleanly reset.
Location: utilities/dropdown.py:479-480 in SelectBatterSub.callback()
Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Multiple fixes to resolve PlayNotFoundException and lineup initialization errors:
1. gauntlets.py:
- Fixed Team object subscriptable errors (use .id instead of ['id'])
- Added fallback cardsets (24, 25, 26) for Event 9 RP shortage
- Fixed draft_team type handling (can be Team object or dict)
2. cogs/gameplay.py:
- Fixed gauntlet game creation flow to read field player lineup from sheets
- Catches LineupsMissingException when SP not yet selected
- Instructs user to run /gamestate after SP selection
3. utilities/dropdown.py:
- Fixed SelectStartingPitcher to create own session instead of using closed session
- Store game/team IDs instead of objects to avoid detached session issues
- Added exception handling for failed legality check API calls
These changes fix the issue where gauntlet games would fail to initialize
because the SP lineup entry wasn't being committed to the database.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Handle gamestates without full lineups
Added /set command for lineup and SP
Fixed uncapped hit bugs
Added league_name property to Games
Fix get_team for gauntlets
Fixed SelectSP dropdown bug