fix: prevent partial DB writes on scorecard submission failure #80
No reviewers
Labels
No Label
ai-changes-requested
ai-pr-opened
ai-reviewed
ai-reviewing
ai-working
in-next-release
status/in-progress
status/pr-open
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: cal/major-domo-v2#80
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "fix/scorecard-submission-resilience-v2"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
#N/A)SheetsExceptionmessages through the error chain instead of wrapping them in generic "Unable to read pitching decisions"SheetsExceptionhandler that shows users the specific cell and error type instead of "An unexpected error occurred"Context
On 2026-03-10, a CLS vs MKE scorecard submission failed because the Pitcherstats tab had a
#N/Aformula error inteam_id(Row 4). The old code had already committed 74 plays and updated the game score before attempting to read pitching decisions — leaving the DB in a partial state. The user saw only: "An unexpected error occurred. Please try again or contact an admin."What changed
commands/league/submit_scorecard.py: Moved all three spreadsheet reads into a single Phase 6 before any DB writes (Phase 7-9). Addedexcept SheetsExceptionhandler with user-friendly detailed message.services/sheets_service.py: Addedexcept SheetsException: raiseinread_pitching_decisionsandread_box_scoreso detailed error messages aren't swallowed by the generic wrapper.Test plan
#N/Aformula error — verify no data written to DB and user sees detailed errorCloses #78
Supersedes #79
🤖 Generated with Claude Code
AI Code Review
Files Reviewed
commands/league/submit_scorecard.py(modified)services/sheets_service.py(modified)Findings
Correctness
read_playtable_data,read_box_score,read_pitching_decisions) are now consolidated into Phase 6 before any DB writes. This eliminates the root cause of the 2026-03-10 incident where 74 plays and a game score were committed before the Pitcherstats#N/Aerror surfaced.read_pitching_decisionsalready raisesSheetsExceptiondirectly (atsheets_service.py:372) when it detects a formula error. Without theexcept SheetsException: raiseguard, that exception was being caught by the genericexcept Exceptionhandler and re-raised as the opaque"Unable to read pitching decisions"— losing the cell location and error type. The fix is correct.read_box_scoreSheetsExceptionguard is defensive but harmless.read_box_scoredoesn't internally raiseSheetsExceptiontoday, so the guard is a no-op that adds consistency and future-proofing.GAME_PATCHEDand needed a rollback; the new code validates in Phase 6 before any writes, so the rollback is correctly omitted.SheetsExceptionis already imported insubmit_scorecard.py(from exceptions import SheetsException, APIException). No import issues.Security
Style & Conventions
self.logger.error(..., error=e),await interaction.edit_original_response(...), structured phase comments.get_client()calls outside service layer.Suggestions
Dead rollback code in
SheetsExceptionhandler. With all sheet reads now in Phase 6, aSheetsExceptioncan only be raised before any DB write, meaningrollback_stateis alwaysNonewhen this handler fires. Theif rollback_state and game_id:block (submit_scorecard.py:358-365) is unreachable dead code. It's harmless and defensive (guards against future regressions if someone adds a sheet read after DB writes), but it may be confusing to readers who expect the handler to actually roll back. Worth a comment like# Note: SheetsException is only raised during Phase 6 reads (before any DB writes), so rollback_state is always None here.— or simply remove it for clarity.Phase numbering gap. After the PR, phase labels in the code jump from 9 (
POST Decisions) to 12 (Create Results Embed), skipping 10–11. These labels are cosmetic, but the gap may be confusing during future maintenance. Could renumber to 10, 11, 12 or just remove the phase labels that are now out of order.read_playtable_datadoesn't need theSheetsExceptionguard (it never raises one internally), so its absence is correct — no action needed.Verdict: COMMENT
The fix is correct, complete, and directly addresses the production incident. No bugs, no security issues, follows all project conventions. Suggestions above are cosmetic/defensive and don't block merging.
Automated review by Claude PR Reviewer