fix(gameplay): replace bare except with NoResultFound in cache-miss paths #163

Open
cal wants to merge 1 commits from autonomous/fix-gameplay-queries-bare-except into main
Owner

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

  • `pytest` passes locally (pre-existing infra errors unrelated to this change)
  • Manual smoke on dev bot (reviewer to confirm)

🤖 Generated via Paper Dynasty autonomous pipeline (2026-04-10)

## 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 - [x] \`pytest\` passes locally (pre-existing infra errors unrelated to this change) - [ ] Manual smoke on dev bot (reviewer to confirm) 🤖 Generated via Paper Dynasty autonomous pipeline (2026-04-10)
cal added 1 commit 2026-04-10 15:28:54 +00:00
fix(gameplay): replace bare except with NoResultFound in cache-miss paths
All checks were successful
Ruff Lint / lint (pull_request) Successful in 27s
f62e08889f
In gameplay_queries.py, 10 try/except blocks used `except Exception:` to
detect SQLModel row-not-found cache misses (.one() returning no result).
This silently swallowed connection failures, attribute errors, and
programming bugs on the gameplay hot path.

Narrowed each handler to `except NoResultFound:` (sqlalchemy.exc).
Real errors now propagate instead of being misinterpreted as cache misses.

Refs: autonomous-pipeline finding analyst-2026-04-10-003
Category: stability / error_handling

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cal added the
autonomous
size:S
type:stability
labels 2026-04-10 15:29:14 +00:00
cal reviewed 2026-04-10 15:38:31 +00:00
cal left a comment
Author
Owner

AI Code Review

Files Reviewed

  • in_game/gameplay_queries.py (modified)

Findings

Correctness

Critical: cache_playersession.get() never raises NoResultFound

The except NoResultFound: substitution in cache_player is incorrect. That try block uses session.get(Player, player_id) — not session.exec(...).one(). session.get() is a primary-key lookup that returns None on a miss; it never raises NoResultFound. The nine other narrowings are correct because they all call .one().

What the original code actually did:
session.get() returns None, then the loop for key, value in db_player.model_dump(...).items(): setattr(this_player, key, value) raises AttributeError (setattr on None). The original except Exception: caught that AttributeError and treated it as "not found, add to db". Ugly, but functional.

What the new code does:
The except NoResultFound: clause is unreachable. When player_id is absent from the session, this_player is None, setattr(None, ...) raises AttributeError, and that exception propagates uncaught up the call stack. The session.add(db_player) cache-write path is dead.

Impact: Any call to get_player_or_none for a player not already in the local SQLModel session will now raise AttributeError at the bot level rather than populating the cache and returning a Player.

Fix required: Either change session.get(Player, player_id) to session.exec(select(Player).where(Player.id == player_id)).one() so NoResultFound can actually be raised, or guard on None explicitly:

this_player = session.get(Player, player_id)
if this_player is None:
    raise NoResultFound

The remaining nine narrowings (cache_team, all card/ratings blocks) are correct — they all use .one() which does raise NoResultFound.

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

  • After fixing cache_player, consider adding an explicit if this_player is None: raise NoResultFound guard as a pattern for all session.get() cache sites for clarity.
  • The PR description accurately describes the problem it intended to solve; the fix just needs to cover the session.get() site.

Verdict: REQUEST_CHANGES

One of the ten narrowings is incorrect. cache_player uses session.get() which returns None on miss rather than raising NoResultFound. The new except NoResultFound: clause is unreachable there, breaking the player cache-write path at runtime with an uncaught AttributeError. 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 **Critical: `cache_player` — `session.get()` never raises `NoResultFound`** The `except NoResultFound:` substitution in `cache_player` is incorrect. That `try` block uses `session.get(Player, player_id)` — not `session.exec(...).one()`. `session.get()` is a primary-key lookup that returns `None` on a miss; it never raises `NoResultFound`. The nine other narrowings are correct because they all call `.one()`. **What the original code actually did:** `session.get()` returns `None`, then the loop `for key, value in db_player.model_dump(...).items(): setattr(this_player, key, value)` raises `AttributeError` (setattr on `None`). The original `except Exception:` caught that `AttributeError` and treated it as "not found, add to db". Ugly, but functional. **What the new code does:** The `except NoResultFound:` clause is unreachable. When `player_id` is absent from the session, `this_player` is `None`, `setattr(None, ...)` raises `AttributeError`, and that exception propagates uncaught up the call stack. The `session.add(db_player)` cache-write path is dead. **Impact:** Any call to `get_player_or_none` for a player not already in the local SQLModel session will now raise `AttributeError` at the bot level rather than populating the cache and returning a Player. **Fix required:** Either change `session.get(Player, player_id)` to `session.exec(select(Player).where(Player.id == player_id)).one()` so `NoResultFound` can actually be raised, or guard on `None` explicitly: ```python this_player = session.get(Player, player_id) if this_player is None: raise NoResultFound ``` The remaining nine narrowings (`cache_team`, all card/ratings blocks) are correct — they all use `.one()` which does raise `NoResultFound`. #### 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 - After fixing `cache_player`, consider adding an explicit `if this_player is None: raise NoResultFound` guard as a pattern for all `session.get()` cache sites for clarity. - The PR description accurately describes the problem it intended to solve; the fix just needs to cover the `session.get()` site. ### Verdict: REQUEST_CHANGES One of the ten narrowings is incorrect. `cache_player` uses `session.get()` which returns `None` on miss rather than raising `NoResultFound`. The new `except NoResultFound:` clause is unreachable there, breaking the player cache-write path at runtime with an uncaught `AttributeError`. The other nine sites are correct and the import is correct. --- *Automated review by Claude PR Reviewer*
Claude added the
ai-reviewing
label 2026-04-10 18:26:11 +00:00
Claude approved these changes 2026-04-10 18:28:08 +00:00
Claude left a comment
Collaborator

AI Code Review

Files Reviewed

  • in_game/gameplay_queries.py (modified)

Findings

Correctness

No issues found. All 10 substitutions are mechanically correct:

  • Every try block's first statement is session.exec(select(...)).one(), which is the SQLAlchemy call that raises NoResultFound when no row matches.
  • Narrowing to except NoResultFound: is the right fix — the old except Exception: had two silent failure modes:
    1. Connection / programming errors caught and misinterpreted as "cache miss" → now propagate correctly.
    2. MultipleResultsFound (raised by .one() when >1 rows match) silently treated as "not found" and a new row inserted → now propagates correctly.
  • For the 4 cases where session.commit() and session.refresh() live inside the try block (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 NoResultFound is the correct import; SQLModel runs on SQLAlchemy and this is the standard exception for .one() no-row results.
  • PR claims 10 sites changed; confirmed exactly 10 in the diff.

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

  • Merge order awareness: PR #165 (feat(compare)) bundles these same gameplay_queries.py changes. 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.
  • No other concerns.

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

## AI Code Review ### Files Reviewed - `in_game/gameplay_queries.py` (modified) ### Findings #### Correctness No issues found. All 10 substitutions are mechanically correct: - Every `try` block's first statement is `session.exec(select(...)).one()`, which is the SQLAlchemy call that raises `NoResultFound` when no row matches. - Narrowing to `except NoResultFound:` is the right fix — the old `except Exception:` had two silent failure modes: 1. **Connection / programming errors** caught and misinterpreted as "cache miss" → now propagate correctly. 2. **`MultipleResultsFound`** (raised by `.one()` when >1 rows match) silently treated as "not found" and a new row inserted → now propagates correctly. - For the 4 cases where `session.commit()` and `session.refresh()` live inside the `try` block (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 NoResultFound` is the correct import; SQLModel runs on SQLAlchemy and this is the standard exception for `.one()` no-row results. - PR claims 10 sites changed; confirmed exactly 10 in the diff. #### 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 - **Merge order awareness**: PR #165 (`feat(compare)`) bundles these same `gameplay_queries.py` changes. 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. - No other concerns. ### 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*
Claude added
ai-reviewed
and removed
ai-reviewing
labels 2026-04-10 18:28:42 +00:00
Claude removed the
ai-reviewed
label 2026-04-11 19:23:47 +00:00
All checks were successful
Ruff Lint / lint (pull_request) Successful in 27s
This pull request is blocked because it's outdated.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin autonomous/fix-gameplay-queries-bare-except:autonomous/fix-gameplay-queries-bare-except
git checkout autonomous/fix-gameplay-queries-bare-except
Sign in to join this conversation.
No description provided.