feat(gauntlet): post completion recap embed on 10-win finish (Roadmap 2.4a) #164

Open
cal wants to merge 3 commits from autonomous/feat-gauntlet-results-recap into main
Owner

Autonomous Pipeline Finding

Source: growth-po nightly sweep (2026-04-10)
Roadmap: Phase 2.4a
Category: feature / engagement

What

On gauntlet completion, bot auto-posts a results recap embed (champion, bracket, prizes) to the gauntlet's channel. Previously completion was silent.

Why

Gauntlets are a core engagement loop but players have no social validation for wins. Public recaps drive winner pride and FOMO for non-participants — standard TCG tournament pattern.

Implementation

  • build_gauntlet_recap_embed(run, event, main_team, rewards) — pure sync builder, gold colour, champion field (team name + user mention), final record, win-progression ladder, prize distribution table with earned/unearned markers.
  • post_gauntlet_recap(run, event, main_team, channel) — async sender; fetches full rewards list then calls builder; gracefully skips if channel is None or any step raises (non-fatal).
  • Hooked into post_result at the wins == 10 completion path.

Test Plan

  • Unit test on recap builder (10 sync tests covering title, colour, champion, record, prizes, empty-rewards case)
  • Async smoke tests: sends embed on success, no-op on None channel, non-fatal on channel.send failure, non-fatal on db_get failure
  • Manual dev gauntlet end-to-end (reviewer)

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

## Autonomous Pipeline Finding **Source:** growth-po nightly sweep (2026-04-10) **Roadmap:** Phase 2.4a **Category:** feature / engagement ## What On gauntlet completion, bot auto-posts a results recap embed (champion, bracket, prizes) to the gauntlet's channel. Previously completion was silent. ## Why Gauntlets are a core engagement loop but players have no social validation for wins. Public recaps drive winner pride and FOMO for non-participants — standard TCG tournament pattern. ## Implementation - `build_gauntlet_recap_embed(run, event, main_team, rewards)` — pure sync builder, gold colour, champion field (team name + user mention), final record, win-progression ladder, prize distribution table with earned/unearned markers. - `post_gauntlet_recap(run, event, main_team, channel)` — async sender; fetches full rewards list then calls builder; gracefully skips if channel is None or any step raises (non-fatal). - Hooked into `post_result` at the `wins == 10` completion path. ## Test Plan - [x] Unit test on recap builder (10 sync tests covering title, colour, champion, record, prizes, empty-rewards case) - [x] Async smoke tests: sends embed on success, no-op on None channel, non-fatal on channel.send failure, non-fatal on db_get failure - [ ] Manual dev gauntlet end-to-end (reviewer) 🤖 Generated via Paper Dynasty autonomous pipeline (2026-04-10)
cal added 2 commits 2026-04-10 15:32:15 +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>
feat(gauntlet): post completion recap embed on 10-win gauntlet finish
All checks were successful
Ruff Lint / lint (pull_request) Successful in 13s
9228d2e66c
Adds build_gauntlet_recap_embed (sync builder) and post_gauntlet_recap
(async sender) to gauntlets.py.  Called from post_result when wins == 10.
Embed shows champion name + user mention, final record, win-progression
ladder, and full prize-distribution table with earned/unearned markers.
Gracefully skips if channel is None or any step raises.

Roadmap 2.4a — closes growth-sweep finding 2026-04-10-003.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
cal added the
autonomous
size:S
type:feature
labels 2026-04-10 15:32:38 +00:00
cal reviewed 2026-04-10 15:39:38 +00:00
cal left a comment
Author
Owner

AI Code Review

Files Reviewed

  • gauntlets.py (modified — new functions + hook)
  • in_game/gameplay_queries.py (modified — formatting/refactor only)
  • tests/test_gauntlet_recap.py (added)

Findings

Correctness

Bug: loss_max or 99 is falsy-unsafe when loss_max=0 — wrong marker on perfect-bonus tier

In build_gauntlet_recap_embed, the marker logic for the prize distribution table is:

marker = "✅" if earned else "❌" if losses > (loss_max or 99) else "⬜"

The expression loss_max or 99 evaluates 0 as falsy and substitutes 99. For a reward with loss_max=0 (the 10-0 perfect-run bonus), any player with losses >= 1 will always evaluate losses > 99 as False, and receive ("not yet reached") instead of ("definitively missed"). This is incorrect — a 10-1 finisher did not miss the perfect-bonus due to a pending condition.

Fix:

marker = "✅" if earned else "❌" if loss_max is not None and losses > loss_max else "⬜"

The existing test test_unearned_perfect_bonus_marked_correctly only asserts "✅" not in bonus_line, which passes for both and , so it does not catch this bug. After applying the fix, tighten the assertion to assert "❌" in bonus_line.

Verdict if this were a two-user repo: REQUEST_CHANGES (single correctness bug; everything else is clean).


Security

No issues found. The <@gmid> mention is sourced from a server-controlled API integer, not user input. No PII is logged or leaked.


Style & Conventions

  • gameplay_queries.py changes are pure formatting plus narrowing except Exceptionexcept NoResultFound — both improvements.
  • Hook placement is correct: fires only inside is_win=True after the wins == 10 branch, never on a loss or mid-run win.
  • post_gauntlet_recap correctly wraps all work in a single try/except Exception so no failure path can interrupt gauntlet completion.
  • Docstrings on both new functions are thorough.

Suggestions (non-blocking)

  • After fixing the marker bug, tighten test_unearned_perfect_bonus_marked_correctly to assert "❌" in bonus_line.
  • post_gauntlet_recap re-fetches all rewards via db_get even though post_result already fetched per-milestone rewards. Minor redundancy (one extra API call); acceptable given the non-fatal wrapper, but worth noting for future optimisation.

Summary

One correctness bug: loss_max or 99 misclassifies the perfect-run bonus as "pending" instead of "missed" for any run with losses > 0. One-line fix. Hook logic, failure isolation, security, and overall test structure are solid. Do not merge until the marker bug is fixed.


Automated review by Claude PR Reviewer — verdict: REQUEST_CHANGES (posted as COMMENT due to single-user repo restriction)

## AI Code Review ### Files Reviewed - `gauntlets.py` (modified — new functions + hook) - `in_game/gameplay_queries.py` (modified — formatting/refactor only) - `tests/test_gauntlet_recap.py` (added) --- ### Findings #### Correctness **Bug: `loss_max or 99` is falsy-unsafe when `loss_max=0` — wrong marker on perfect-bonus tier** In `build_gauntlet_recap_embed`, the marker logic for the prize distribution table is: ```python marker = "✅" if earned else "❌" if losses > (loss_max or 99) else "⬜" ``` The expression `loss_max or 99` evaluates `0` as falsy and substitutes `99`. For a reward with `loss_max=0` (the 10-0 perfect-run bonus), any player with `losses >= 1` will always evaluate `losses > 99` as `False`, and receive `⬜` ("not yet reached") instead of `❌` ("definitively missed"). This is incorrect — a 10-1 finisher did not miss the perfect-bonus due to a pending condition. Fix: ```python marker = "✅" if earned else "❌" if loss_max is not None and losses > loss_max else "⬜" ``` The existing test `test_unearned_perfect_bonus_marked_correctly` only asserts `"✅" not in bonus_line`, which passes for both `⬜` and `❌`, so it does not catch this bug. After applying the fix, tighten the assertion to `assert "❌" in bonus_line`. **Verdict if this were a two-user repo: REQUEST_CHANGES** (single correctness bug; everything else is clean). --- #### Security No issues found. The `<@gmid>` mention is sourced from a server-controlled API integer, not user input. No PII is logged or leaked. --- #### Style & Conventions - `gameplay_queries.py` changes are pure formatting plus narrowing `except Exception` → `except NoResultFound` — both improvements. - Hook placement is correct: fires only inside `is_win=True` after the `wins == 10` branch, never on a loss or mid-run win. - `post_gauntlet_recap` correctly wraps all work in a single `try/except Exception` so no failure path can interrupt gauntlet completion. - Docstrings on both new functions are thorough. --- #### Suggestions (non-blocking) - After fixing the marker bug, tighten `test_unearned_perfect_bonus_marked_correctly` to assert `"❌" in bonus_line`. - `post_gauntlet_recap` re-fetches all rewards via `db_get` even though `post_result` already fetched per-milestone rewards. Minor redundancy (one extra API call); acceptable given the non-fatal wrapper, but worth noting for future optimisation. --- ### Summary One correctness bug: `loss_max or 99` misclassifies the perfect-run bonus as "pending" instead of "missed" for any run with losses > 0. One-line fix. Hook logic, failure isolation, security, and overall test structure are solid. **Do not merge until the marker bug is fixed.** --- *Automated review by Claude PR Reviewer — verdict: REQUEST_CHANGES (posted as COMMENT due to single-user repo restriction)*
Claude added the
ai-reviewing
label 2026-04-10 16:56:57 +00:00
Claude requested changes 2026-04-10 16:58:54 +00:00
Dismissed
Claude left a comment
Collaborator

AI Code Review

Files Reviewed

  • gauntlets.py (modified — new build_gauntlet_recap_embed, post_gauntlet_recap, hook in post_result)
  • in_game/gameplay_queries.py (modified — import reformatting + NoResultFound narrowing)
  • tests/test_gauntlet_recap.py (added — 14 tests for builder + async sender)

Findings

Correctness

[BLOCKING] gauntlets.py(loss_max or 99) falsy-zero bug in prize marker logic

marker = "✅" if earned else "❌" if losses > (loss_max or 99) else "⬜"

When loss_max=0 (the perfect-run bonus, e.g. "10-0"), Python evaluates (0 or 99)99. A player finishing 10-1 sees losses > 99 → False → "⬜" instead of "❌". The marker for a missed perfect-run reward is silently replaced with a neutral indicator.

Fix:

marker = "✅" if earned else "❌" if losses > (loss_max if loss_max is not None else 99) else "⬜"

[BLOCKING] tests/test_gauntlet_recap.py:test_unearned_perfect_bonus_marked_correctly — test does not catch the bug above

assert "✅" not in bonus_line

This assertion only verifies that the is absent, not that is present. The test passes even with the (loss_max or 99) bug because "⬜" also satisfies "✅" not in bonus_line. The test needs:

assert "❌" in bonus_line

Security

No issues found.

Style & Conventions

Scope creep — in_game/gameplay_queries.py changes belong in PR #163

This PR bundles in_game/gameplay_queries.py (import reformatting + NoResultFound narrowing) alongside the gauntlet recap feature. PR #163 ("fix(gameplay): replace bare except with NoResultFound") is still open and targets the same file. The overlap creates merge conflict risk and makes both PRs harder to review independently. The gameplay_queries changes should be limited to PR #163; this PR should only contain gauntlets.py and tests/test_gauntlet_recap.py.

Suggestions

  • post_result hook at line if this_run["wins"] == 10 is correct — wins == 10 is the gauntlet completion threshold and this_run is already updated before this point.
  • Non-fatal error handling in post_gauntlet_recap (bare except Exception + logger.warning) is the right pattern for a recap side-effect that must never interrupt game flow.
  • The post_gauntlet_recap function correctly uses this_event["id"] inside the try block — a missing id key would be caught and logged, not crash the bot.

Verdict: REQUEST_CHANGES

One correctness bug: (loss_max or 99) treats loss_max=0 as 99, causing a missed perfect-run reward to display instead of . The existing test does not catch this. Fix both the production code and the assertion before merge.


Automated review by Claude PR Reviewer

## AI Code Review ### Files Reviewed - `gauntlets.py` (modified — new `build_gauntlet_recap_embed`, `post_gauntlet_recap`, hook in `post_result`) - `in_game/gameplay_queries.py` (modified — import reformatting + `NoResultFound` narrowing) - `tests/test_gauntlet_recap.py` (added — 14 tests for builder + async sender) --- ### Findings #### Correctness **[BLOCKING] `gauntlets.py` — `(loss_max or 99)` falsy-zero bug in prize marker logic** ```python marker = "✅" if earned else "❌" if losses > (loss_max or 99) else "⬜" ``` When `loss_max=0` (the perfect-run bonus, e.g. "10-0"), Python evaluates `(0 or 99)` → `99`. A player finishing 10-1 sees `losses > 99` → False → `"⬜"` instead of `"❌"`. The ❌ marker for a missed perfect-run reward is silently replaced with a neutral indicator. Fix: ```python marker = "✅" if earned else "❌" if losses > (loss_max if loss_max is not None else 99) else "⬜" ``` **[BLOCKING] `tests/test_gauntlet_recap.py:test_unearned_perfect_bonus_marked_correctly` — test does not catch the bug above** ```python assert "✅" not in bonus_line ``` This assertion only verifies that the ✅ is absent, not that ❌ is present. The test passes even with the `(loss_max or 99)` bug because `"⬜"` also satisfies `"✅" not in bonus_line`. The test needs: ```python assert "❌" in bonus_line ``` #### Security No issues found. #### Style & Conventions **Scope creep — `in_game/gameplay_queries.py` changes belong in PR #163** This PR bundles `in_game/gameplay_queries.py` (import reformatting + `NoResultFound` narrowing) alongside the gauntlet recap feature. PR #163 ("fix(gameplay): replace bare except with NoResultFound") is still open and targets the same file. The overlap creates merge conflict risk and makes both PRs harder to review independently. The gameplay_queries changes should be limited to PR #163; this PR should only contain `gauntlets.py` and `tests/test_gauntlet_recap.py`. #### Suggestions - `post_result` hook at line `if this_run["wins"] == 10` is correct — wins == 10 is the gauntlet completion threshold and `this_run` is already updated before this point. - Non-fatal error handling in `post_gauntlet_recap` (bare `except Exception` + `logger.warning`) is the right pattern for a recap side-effect that must never interrupt game flow. - The `post_gauntlet_recap` function correctly uses `this_event["id"]` inside the try block — a missing `id` key would be caught and logged, not crash the bot. --- ### Verdict: REQUEST_CHANGES One correctness bug: `(loss_max or 99)` treats `loss_max=0` as `99`, causing a missed perfect-run reward to display `⬜` instead of `❌`. The existing test does not catch this. Fix both the production code and the assertion before merge. --- *Automated review by Claude PR Reviewer*
Claude added
ai-changes-requested
and removed
ai-reviewing
labels 2026-04-10 16:59:28 +00:00
Claude added the
ai-working
label 2026-04-10 18:00:50 +00:00
cal added 1 commit 2026-04-10 18:03:31 +00:00
fix: address review feedback (#164)
All checks were successful
Ruff Lint / lint (pull_request) Successful in 17s
079f9ac4f6
- Fix falsy-zero bug: `(loss_max or 99)` → `loss_max is not None and losses > loss_max`
  so a player finishing 10-1 sees  on the perfect-run bonus instead of 
- Tighten test assertion: assert "" in bonus_line (was: assert "" not in bonus_line)
- Revert in_game/gameplay_queries.py to main — formatting/NoResultFound changes
  belong in PR #163, not here

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Collaborator

Review Feedback Addressed (079f9ac)

Blocking fixes

1. loss_max or 99 falsy-zero bug — fixed

gauntlets.py:2475 — changed:

# before
marker = "✅" if earned else "❌" if losses > (loss_max or 99) else "⬜"

# after
marker = "✅" if earned else "❌" if loss_max is not None and losses > loss_max else "⬜"

A player finishing 10-1 now correctly sees on the 10-0 perfect-run bonus instead of .

2. Tightened test assertion — fixed

tests/test_gauntlet_recap.py:test_unearned_perfect_bonus_marked_correctly — changed:

# before (passes for both ❌ and ⬜ — didn't catch the bug)
assert "✅" not in bonus_line

# after
assert "❌" in bonus_line

Scope cleanup

3. Reverted in_game/gameplay_queries.py to main

The import reformatting and NoResultFound narrowing in this file belong in PR #163. Restored to main state to eliminate the merge conflict risk and keep this PR scoped to the gauntlet recap feature only.

## Review Feedback Addressed (079f9ac) ### Blocking fixes **1. `loss_max or 99` falsy-zero bug — fixed** `gauntlets.py:2475` — changed: ```python # before marker = "✅" if earned else "❌" if losses > (loss_max or 99) else "⬜" # after marker = "✅" if earned else "❌" if loss_max is not None and losses > loss_max else "⬜" ``` A player finishing 10-1 now correctly sees `❌` on the 10-0 perfect-run bonus instead of `⬜`. **2. Tightened test assertion — fixed** `tests/test_gauntlet_recap.py:test_unearned_perfect_bonus_marked_correctly` — changed: ```python # before (passes for both ❌ and ⬜ — didn't catch the bug) assert "✅" not in bonus_line # after assert "❌" in bonus_line ``` ### Scope cleanup **3. Reverted `in_game/gameplay_queries.py` to `main`** The import reformatting and `NoResultFound` narrowing in this file belong in PR #163. Restored to main state to eliminate the merge conflict risk and keep this PR scoped to the gauntlet recap feature only.
Claude removed the
ai-working
ai-changes-requested
labels 2026-04-10 18:03:53 +00:00
Claude added the
ai-reviewing
label 2026-04-10 18:23:07 +00:00
Claude approved these changes 2026-04-10 18:25:18 +00:00
Dismissed
Claude left a comment
Collaborator

AI Code Review

Files Reviewed

  • gauntlets.py (modified — build_gauntlet_recap_embed, post_gauntlet_recap, hook in post_result)
  • tests/test_gauntlet_recap.py (added)

Findings

Correctness

Previous blocking issues resolved:

  1. loss_max=0 bug — fixed. Prior version used (loss_max or 99) which treated loss_max=0 as 99. Current code uses loss_max == 0 for label and loss_max is not None for earned/marker checks. Python correctly evaluates 0 is not None as True, so the 10-0 perfect-run bonus now correctly produces when losses > 0.

  2. Test assertion strengthened — fixed. test_unearned_perfect_bonus_marked_correctly now asserts "❌" in bonus_line (was asserting absence of , which didn't catch the bug). Traced: wins=10, losses=1, loss_max=0 → earned=False, losses > loss_max → marker . Test passes and regression-catches the prior bug.

  3. Scope issue — resolved. in_game/gameplay_queries.py is no longer in the diff. No merge conflict risk with PR #163.

No new correctness issues found.

Security

No issues found. No user input reaches queries or commands. gmid is interpolated into a Discord mention string (<@{gmid}>) — safe within Discord embed context; no HTML/injection risk.

Style & Conventions

  • Non-fatal pattern consistent with project (try/except Exception + logger.warning(exc_info=True)).
  • None channel guard logs and returns early — matches existing patterns in the codebase.
  • GOLD = 0xFFD700 defined inside the function body; could be module-level, but this is stylistic preference only and not a blocker.

Suggestions

  • win_num = r.get("win_num", "?") — if a malformed reward row omits win_num, the "?" string would cause TypeError in the wins >= win_num comparison. The outer try/except in post_gauntlet_recap catches this gracefully, but a "?" default that can't be compared to an int is a latent footgun. Consider win_num = r.get("win_num") and skipping rows where win_num is None. Not a blocker given the try/except guard.

Verdict: APPROVED

Both blocking issues from the prior review are fixed with correct logic and strengthened tests. Implementation is clean, non-fatal, and scoped correctly to gauntlets.py. Ready to merge.


Automated review by Claude PR Reviewer

## AI Code Review ### Files Reviewed - `gauntlets.py` (modified — `build_gauntlet_recap_embed`, `post_gauntlet_recap`, hook in `post_result`) - `tests/test_gauntlet_recap.py` (added) ### Findings #### Correctness Previous blocking issues resolved: 1. **`loss_max=0` bug — fixed.** Prior version used `(loss_max or 99)` which treated `loss_max=0` as `99`. Current code uses `loss_max == 0` for label and `loss_max is not None` for `earned`/marker checks. Python correctly evaluates `0 is not None` as `True`, so the 10-0 perfect-run bonus now correctly produces `❌` when `losses > 0`. 2. **Test assertion strengthened — fixed.** `test_unearned_perfect_bonus_marked_correctly` now asserts `"❌" in bonus_line` (was asserting absence of `✅`, which didn't catch the bug). Traced: wins=10, losses=1, loss_max=0 → earned=False, losses > loss_max → marker `❌`. Test passes and regression-catches the prior bug. 3. **Scope issue — resolved.** `in_game/gameplay_queries.py` is no longer in the diff. No merge conflict risk with PR #163. No new correctness issues found. #### Security No issues found. No user input reaches queries or commands. `gmid` is interpolated into a Discord mention string (`<@{gmid}>`) — safe within Discord embed context; no HTML/injection risk. #### Style & Conventions - Non-fatal pattern consistent with project (`try/except Exception` + `logger.warning(exc_info=True)`). - `None` channel guard logs and returns early — matches existing patterns in the codebase. - `GOLD = 0xFFD700` defined inside the function body; could be module-level, but this is stylistic preference only and not a blocker. #### Suggestions - `win_num = r.get("win_num", "?")` — if a malformed reward row omits `win_num`, the `"?"` string would cause `TypeError` in the `wins >= win_num` comparison. The outer `try/except` in `post_gauntlet_recap` catches this gracefully, but a `"?"` default that can't be compared to an int is a latent footgun. Consider `win_num = r.get("win_num")` and skipping rows where `win_num is None`. Not a blocker given the try/except guard. ### Verdict: APPROVED Both blocking issues from the prior review are fixed with correct logic and strengthened tests. Implementation is clean, non-fatal, and scoped correctly to `gauntlets.py`. Ready to merge. --- *Automated review by Claude PR Reviewer*
Claude added
ai-reviewed
and removed
ai-reviewing
labels 2026-04-10 18:26:09 +00:00
Claude removed the
ai-reviewed
label 2026-04-11 19:23:47 +00:00
claude-reviewer added the
ai-reviewing
label 2026-04-11 21:27:20 +00:00
claude-reviewer approved these changes 2026-04-11 21:29:03 +00:00
Dismissed
claude-reviewer left a comment
Collaborator

AI Code Review

Files Reviewed

  • gauntlets.py (modified — build_gauntlet_recap_embed, post_gauntlet_recap, hook in post_result)
  • tests/test_gauntlet_recap.py (added — 315 lines, 14 tests)

Findings

Correctness

No issues found.

  • Hook placement verified: added inside if is_win: block after await channel.send(...) at line 2546. All four args (this_run, this_event, main_team, channel) are in scope at that point.
  • this_run["wins"] at the check point reflects the already-incremented value from db_patch — the == 10 test is correct.
  • earned ternary parses correctly: wins >= win_num and losses <= loss_max if loss_max is not None else wins >= win_num.
  • marker logic correctly distinguishes (earned) / (definitively missed, losses > loss_max) / (pending).
  • Prior blocking issues confirmed resolved: loss_max == 0 label guard, loss_max is not None earned guard, "❌" in bonus_line test assertion.

Security

No issues found. gmid used in Discord mention format comes from the teams API dict, not user input. No hardcoded credentials.

Style & Conventions

No issues found. Non-fatal pattern, try/except with logger.warning(exc_info=True), and db_get call style all consistent with the codebase.

Suggestions

  • gauntlets.pywin_num = r.get("win_num", "?") (prize loop): the "?" default is used for the display label but is also implicitly compared via wins >= win_num — if win_num is ever "?", a TypeError propagates up to post_gauntlet_recap's outer except Exception. Non-blocking since it's caught, and API responses should always include win_num, but r.get("win_num", 0) would be a safer default that degrades gracefully instead of raising.

Verdict: APPROVED

Clean implementation. Logic is correct, non-fatal pattern is consistent with the codebase, and test coverage is thorough (builder unit tests + async smoke tests covering the None channel, send failure, and db_get failure paths). Ready to merge.


Automated review by Claude PR Reviewer

## AI Code Review ### Files Reviewed - `gauntlets.py` (modified — `build_gauntlet_recap_embed`, `post_gauntlet_recap`, hook in `post_result`) - `tests/test_gauntlet_recap.py` (added — 315 lines, 14 tests) ### Findings #### Correctness No issues found. - Hook placement verified: added inside `if is_win:` block after `await channel.send(...)` at line 2546. All four args (`this_run`, `this_event`, `main_team`, `channel`) are in scope at that point. - `this_run["wins"]` at the check point reflects the already-incremented value from `db_patch` — the `== 10` test is correct. - `earned` ternary parses correctly: `wins >= win_num and losses <= loss_max if loss_max is not None else wins >= win_num`. - `marker` logic correctly distinguishes ✅ (earned) / ❌ (definitively missed, losses > loss_max) / ⬜ (pending). - Prior blocking issues confirmed resolved: `loss_max == 0` label guard, `loss_max is not None` earned guard, `"❌" in bonus_line` test assertion. #### Security No issues found. `gmid` used in Discord mention format comes from the teams API dict, not user input. No hardcoded credentials. #### Style & Conventions No issues found. Non-fatal pattern, try/except with `logger.warning(exc_info=True)`, and `db_get` call style all consistent with the codebase. #### Suggestions - `gauntlets.py` — `win_num = r.get("win_num", "?")` (prize loop): the `"?"` default is used for the display label but is also implicitly compared via `wins >= win_num` — if `win_num` is ever `"?"`, a `TypeError` propagates up to `post_gauntlet_recap`'s outer `except Exception`. Non-blocking since it's caught, and API responses should always include `win_num`, but `r.get("win_num", 0)` would be a safer default that degrades gracefully instead of raising. ### Verdict: APPROVED Clean implementation. Logic is correct, non-fatal pattern is consistent with the codebase, and test coverage is thorough (builder unit tests + async smoke tests covering the None channel, send failure, and db_get failure paths). Ready to merge. --- *Automated review by Claude PR Reviewer*
claude-reviewer added
ai-reviewed
and removed
ai-reviewing
labels 2026-04-11 21:29:07 +00:00
Author
Owner

TODO: Remove emojis from output

TODO: Remove emojis from output
cal added
ai-changes-requested
and removed
ai-reviewed
labels 2026-04-12 14:33:51 +00:00
cal dismissed Claude’s review 2026-04-12 14:50:50 +00:00
cal dismissed claude-reviewer’s review 2026-04-12 14:50:55 +00:00
Claude removed the
ai-changes-requested
label 2026-04-12 15:01:17 +00:00
All checks were successful
Ruff Lint / lint (pull_request) Successful in 17s
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/feat-gauntlet-results-recap:autonomous/feat-gauntlet-results-recap
git checkout autonomous/feat-gauntlet-results-recap
Sign in to join this conversation.
No description provided.