Prevents PositionNotFoundException from crashing mlb-campaign when a
player is placed at a position they cannot play (e.g. an outfielder
listed at Catcher in the Google Sheet). Adds early validation in
get_lineups_from_sheets and proper error handling at all read_lineup
call sites so the user gets a clear message and the game is cleaned up.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Changed from 'Auto-Restarted' to 'Restarted' and made the message
generic since the bot restarts for multiple reasons (manual, deployment,
healthcheck) - not just healthcheck failures.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Discord webhooks require a User-Agent header or they return 403 Forbidden.
Added 'Paper-Dynasty-Discord-Bot/1.0' as User-Agent.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Sends instant notifications to Discord when the bot restarts, helping
track stability issues and auto-recovery events.
Changes:
- Add notify_restart.py script to send webhook notifications
- Integrate notification into bot startup (on_ready event)
The notification includes:
- Timestamp of restart (CST)
- Reason (healthcheck failure detection)
- Reference to diagnostics logs
Configuration:
Set RESTART_WEBHOOK_URL environment variable in docker-compose.yml
to enable notifications.
This provides immediate visibility when Docker auto-restarts the bot
due to crashes or healthcheck failures.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Adds idempotency guard to prevent race conditions when multiple users
submit commands for the same play simultaneously.
Changes:
- Add PlayLockedException for locked play detection
- Implement lock check in checks_log_interaction()
- Acquire lock (play.locked = True) before processing commands
- Release lock (play.locked = False) after play completion
- Add warning logs for rejected duplicate submissions
- Add /diagnostics endpoint to health server for debugging
This prevents database corruption and duplicate processing when users
spam commands like "log xcheck" while the first is still processing.
Tested successfully in Discord - duplicate commands now properly return
PlayLockedException with instructions to wait.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Adds two new test cases to test_play_locking.py to improve coverage:
1. test_lock_released_after_successful_completion
- Verifies play.locked is set to False after complete_play()
- Confirms play.complete is set to True
- Validates database commit is called
2. test_different_commands_racing_on_locked_play
- Tests that ANY command type is blocked on locked plays
- Prevents race conditions between different command types
- Tests multiple commands: walk, strikeout, single, xcheck
These tests ensure the play locking idempotency guard works correctly
for both lock acquisition and release, and prevents all command types
from racing (not just duplicate commands).
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
When API returns a pitcher without pitching data (e.g., Ohtani with
pos_1=DH), explicitly fetch pitcherscouting and validate before use.
If validation fails, retry with different sp_rank values.
Retry strategy: increment rank first, if > 5 then decrement from
original rank, ensuring all 5 ranks are tried before giving up.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Card 6605 (Ohtani) placed at position P had pitcherscouting_id=NULL,
causing AttributeError when accessing pitcherscouting.pitchingcard.hand.
Added null check with fallback to batterscouting hand or '?' if neither
scouting record exists.
Also fixed set notation bug on line 240 where {value} created a Python
set instead of a string.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previously p_group was only set if pack type already existed in p_data,
which would silently skip new pack types. Now properly initializes the
pack type list when encountering a new type.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add numpy<2 constraint to requirements.txt to fix RuntimeError on
sba-bots where CPU doesn't support X86_V2 instructions required by
numpy 2.x. This was causing cogs.admins and cogs.gameplay to fail
loading on bot startup.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add FRANCHISE_NORMALIZE dict and helper to constants.py
- Update economy.py to normalize team_choice and use sname
- Update helpers/main.py franchise queries to use sname
- Update selectors.py to normalize franchise on player updates
Part of cross-era player matching fix for AI rosters
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add 'Athletics' alias to ALL_MLB_TEAMS, IMAGES['mvp'], and AL_TEAM_IDS
to support both old franchise name ("Oakland Athletics") and new mlbclub
name ("Athletics") after the team's relocation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
PD_SEASON was set to 9 in helpers/constants.py while games are
recorded in season 10. This caused /player command to return
no stats for cardset 27 cards since they only have season 10 data.
Changes:
- PD_SEASON: 9 → 10
- SBA_SEASON: 11 → 12
- ranked_cardsets: updated to current cardsets
- Added gauntlet-8 and gauntlet-9 configs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>