# Code Review Report: group_horse_racing **Date**: 2026-05-09 **Scope**: 15 Python files, ~200KB **Files Modified**: 4 ## Summary Horse racing plugin with room management, betting system, race simulation, and settlement. Overall architecture is clean (command pattern + engine + store separation). Found 1 critical singleton bug and 1 enum bug. ## Issues Found & Fixed ### FIX 1 — CRITICAL: Dual RoomStore Instances (shared.py) - **Problem**: `shared.py` created its own `RoomStore(Config())` at module level (L18), separate from the singleton in `room_store.py` (L253) managed by `__init__.py` lifecycle hooks. This meant the `startup`/`shutdown` hooks (init DB, cleanup old rooms) operated on a DIFFERENT instance than the commands module. - **Impact**: Race data persistence could silently fail — rooms might not save to DB, old rooms not cleaned up. - **Fix**: Changed `from ..room_store import RoomStore` + `room_store = RoomStore(config)` → `from ..room_store import room_store` (import the singleton). - **Risk**: Low — straightforward import change. ### FIX 2 — BUG: Invalid HorseState.WAITING (race.py L146) - **Problem**: After stopping a race, horses were set to `HorseState.WAITING` which doesn't exist in the enum (only READY/RACING/FINISHED). - **Impact**: Would raise `AttributeError` at runtime if stop-race command was used. - **Fix**: Changed to `HorseState.READY`. - **Risk**: None — enum value now exists. ### FIX 3 — Silent Exceptions → Debug Logging (message_service.py) - **Problem**: Two `except Exception: pass` blocks in `recall_previous_of_type` (L66) and `_schedule_recall` (L81). - **Context**: Message deletion failures (network errors, already deleted). - **Fix**: Added `logger.debug(..., exc_info=True)` for observability. - **Risk**: None — logging only. ### FIX 4 — Silent Exceptions → Debug Logging (test_commands.py) - **Problem**: Two `except Exception: pass` blocks in test cleanup code (L256, L237). - **Fix**: Added `logger.debug(...)` for test debugging. ## Issues Reviewed & Accepted (No Fix Needed) - **config.py:75** — Silent `except ValueError: pass` with fallback to `set()`. Already has warning at L70. Radius-0 operation. - **race.py:77,127** — Admin check silent excepts. Default to non-admin on API failure. Radius-0 operation. - **shared.py:31** — Name lookup fallback to user_id string. Radius-0 operation. - **test_commands.py L266** — `RoomStore()` in `_InMemoryRoomStore` mock. Test-only, acceptable. - **image_render.py** — PIL synchronous rendering. Pre-existing in qqpush plugin (fixed there). Not actionable here as it's the same shared code. ## Architecture Notes - Good separation: RoomStore (persistence) → RaceEngine (logic) → MessageService (messaging) → Commands (handlers) - Singleton pattern for RoomStore with lifecycle management via nonebot hooks - Race simulation runs as asyncio task with tick-based updates - Betting system with odds calculation is well-structured - Test file (413 lines) provides good simulation coverage ## Verification - ✅ 15/15 files syntax valid - ✅ No `HorseState.WAITING` references remain - ✅ `shared.py` imports singleton (no `RoomStore(config)` call) - ✅ Debug logging present in message_service.py and test_commands.py