fix(gameplay): replace bare except with NoResultFound in cache-miss paths #163
No reviewers
Labels
No Label
ai-changes-requested
ai-failed
ai-pr-opened
ai-reviewed
ai-reviewing
ai-working
autonomous
bug
enhancement
feature
in-queue
performance
security
size:M
size:S
tech-debt
tests
todo
type:feature
type:stability
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: cal/paper-dynasty-discord#163
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "autonomous/fix-gameplay-queries-bare-except"
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?
Autonomous Pipeline Finding
Source: analyst nightly audit (2026-04-10)
Severity: high
Category: error_handling
Problem
`in_game/gameplay_queries.py` used `except Exception:` in 10 places to detect SQLModel row-not-found. This silently swallowed real errors (connection failures, programming bugs), potentially corrupting in-progress game state without a visible signal.
Change
Narrowed exception handling to `except NoResultFound:` (sqlalchemy.exc). Real errors now propagate instead of being misinterpreted as cache misses.
Test Plan
🤖 Generated via Paper Dynasty autonomous pipeline (2026-04-10)
AI Code Review
Files Reviewed
in_game/gameplay_queries.py(modified)Findings
Correctness
Critical:
cache_player—session.get()never raisesNoResultFoundThe
except NoResultFound:substitution incache_playeris incorrect. Thattryblock usessession.get(Player, player_id)— notsession.exec(...).one().session.get()is a primary-key lookup that returnsNoneon a miss; it never raisesNoResultFound. The nine other narrowings are correct because they all call.one().What the original code actually did:
session.get()returnsNone, then the loopfor key, value in db_player.model_dump(...).items(): setattr(this_player, key, value)raisesAttributeError(setattr onNone). The originalexcept Exception:caught thatAttributeErrorand treated it as "not found, add to db". Ugly, but functional.What the new code does:
The
except NoResultFound:clause is unreachable. Whenplayer_idis absent from the session,this_playerisNone,setattr(None, ...)raisesAttributeError, and that exception propagates uncaught up the call stack. Thesession.add(db_player)cache-write path is dead.Impact: Any call to
get_player_or_nonefor a player not already in the local SQLModel session will now raiseAttributeErrorat the bot level rather than populating the cache and returning a Player.Fix required: Either change
session.get(Player, player_id)tosession.exec(select(Player).where(Player.id == player_id)).one()soNoResultFoundcan actually be raised, or guard onNoneexplicitly:The remaining nine narrowings (
cache_team, all card/ratings blocks) are correct — they all use.one()which does raiseNoResultFound.Security
No issues found.
Style & Conventions
The import expansion to multi-line form and the single-quote to double-quote normalisation are consistent with the rest of the diff and acceptable, though they are out of scope for the stated purpose of the PR.
Suggestions
cache_player, consider adding an explicitif this_player is None: raise NoResultFoundguard as a pattern for allsession.get()cache sites for clarity.session.get()site.Verdict: REQUEST_CHANGES
One of the ten narrowings is incorrect.
cache_playerusessession.get()which returnsNoneon miss rather than raisingNoResultFound. The newexcept NoResultFound:clause is unreachable there, breaking the player cache-write path at runtime with an uncaughtAttributeError. The other nine sites are correct and the import is correct.Automated review by Claude PR Reviewer
AI Code Review
Files Reviewed
in_game/gameplay_queries.py(modified)Findings
Correctness
No issues found. All 10 substitutions are mechanically correct:
tryblock's first statement issession.exec(select(...)).one(), which is the SQLAlchemy call that raisesNoResultFoundwhen no row matches.except NoResultFound:is the right fix — the oldexcept Exception:had two silent failure modes:MultipleResultsFound(raised by.one()when >1 rows match) silently treated as "not found" and a new row inserted → now propagates correctly.session.commit()andsession.refresh()live inside thetryblock (Team/Player/Card cache functions), the old code would catch commit errors and attempt a second insert — also broken. Propagating these is the right call.from sqlalchemy.exc import NoResultFoundis the correct import; SQLModel runs on SQLAlchemy and this is the standard exception for.one()no-row results.Security
No issues found.
Style & Conventions
The diff includes cosmetic reformatting (single → double quotes, long imports split to multi-line). This is consistent with ruff's default quote style and the project's ruff.toml setup.
Suggestions
feat(compare)) bundles these samegameplay_queries.pychanges. If #165 lands first, this branch will have a merge conflict. Recommend merging this PR first (it's the cleanest isolated fix), then rebasing #165 on top — or closing this one if #165 goes first.Verdict: APPROVED
Clean, focused fix. Exception narrowing is correct at all 10 sites, import is correct, and the change strictly improves error visibility without altering any happy-path behavior.
Automated review by Claude PR Reviewer
Checkout
From your project repository, check out a new branch and test the changes.