fix(bot): a reaction that never reached the backend now says so (#409) #462

Merged
claude-bot merged 1 commit from fix/409-visible-vote-failures into main 2026-08-31 05:11:36 +00:00
Contributor

Closes #409 (MEDIUM). One of the two remaining states gating #416.

The defect

Every failure in the reaction handlers was a log.warning and nothing else. The emoji stays on the message either way, so a player whose vote never reached Quest Board had every reason to believe it landed.

auto_close_voting then confirms whichever slot scored highest from the votes that did arrive, with nothing to say the tally was short. A backend blip during a busy voting window can decide when the group plays, and nobody who voted during it ever finds out.

What ships

A failed reaction DMs the reacting player — what didn't happen, and what to do while it still matters. DM first because the failure is theirs; a channel-wide notice on a shared poll would confuse everyone else. An on the message is the fallback when DMs are closed: worse, but it beats the silence this replaces.

Success stays silent. A DM per vote would be unusable, and the message only works if it is rare.

Covers votes, RSVPs and summary feedback.

The second half: a lost mapping is not "not ours"

A missing message mapping shared one bare return with "someone reacted to an unrelated message". Those are different situations: a tracked emoji on a message this bot posted was a poll whose Redis mapping has expired (30-day TTL) or was lost to a restart while on the in-memory fallback. That now says so, with a different remedy — nothing will make that poll work again, so "try again in a minute" would be wrong.

The discriminator is deliberately narrow: bot-authored AND one of our emojis. The bot posts reminders and summaries too, and a 🎉 on a reminder must not produce a "your vote failed" DM.

One honest limitation, commented in place: message_author_id is only populated on reaction add, not remove. So un-voting against a lost mapping stays silent. That is acceptable — removing a vote that was never recorded changes nothing, and guessing would mean DMing anyone who reacts to anything.

Verification

Mutation-checked: with the notifications disabled, 5 of the 8 new tests fail.

Three of the eight assert silence — a successful vote, an unrelated emoji on a bot message, a tracked emoji on someone else's message — so the change cannot pass by notifying indiscriminately, which is the failure mode that would make people ignore the message.

228 bot tests pass (was 220).

Lint: my two files are clean at pinned ruff 0.4.4. Note that questboard_bot/utils/audio.py:14 has a pre-existing unused os import (last touched in #320) — CI's ruff job covers webapp/backend/ scripts/ only, so bot/ is not gated. Flagging rather than fixing, since it is unrelated and bot/ is excluded from the formatter by project convention.

🤖 Generated with Claude Code

Closes #409 (MEDIUM). One of the two remaining states gating #416. ## The defect Every failure in the reaction handlers was a `log.warning` and nothing else. The emoji stays on the message either way, so a player whose vote never reached Quest Board had every reason to believe it landed. `auto_close_voting` then confirms whichever slot scored highest **from the votes that did arrive**, with nothing to say the tally was short. A backend blip during a busy voting window can decide when the group plays, and nobody who voted during it ever finds out. ## What ships A failed reaction **DMs the reacting player** — what didn't happen, and what to do while it still matters. DM first because the failure is theirs; a channel-wide notice on a shared poll would confuse everyone else. An ❗ on the message is the fallback when DMs are closed: worse, but it beats the silence this replaces. Success stays silent. A DM per vote would be unusable, and the message only works if it is rare. Covers votes, RSVPs and summary feedback. ## The second half: a lost mapping is not "not ours" A missing message mapping shared one bare `return` with "someone reacted to an unrelated message". Those are different situations: a *tracked* emoji on a message **this bot posted** was a poll whose Redis mapping has expired (30-day TTL) or was lost to a restart while on the in-memory fallback. That now says so, with a different remedy — nothing will make that poll work again, so "try again in a minute" would be wrong. **The discriminator is deliberately narrow: bot-authored AND one of our emojis.** The bot posts reminders and summaries too, and a 🎉 on a reminder must not produce a "your vote failed" DM. One honest limitation, commented in place: `message_author_id` is only populated on reaction **add**, not remove. So un-voting against a lost mapping stays silent. That is acceptable — removing a vote that was never recorded changes nothing, and guessing would mean DMing anyone who reacts to anything. ## Verification Mutation-checked: with the notifications disabled, **5 of the 8** new tests fail. Three of the eight assert **silence** — a successful vote, an unrelated emoji on a bot message, a tracked emoji on someone else's message — so the change cannot pass by notifying indiscriminately, which is the failure mode that would make people ignore the message. **228 bot tests pass** (was 220). Lint: my two files are clean at pinned ruff 0.4.4. Note that `questboard_bot/utils/audio.py:14` has a pre-existing unused `os` import (last touched in #320) — CI's ruff job covers `webapp/backend/ scripts/` only, so `bot/` is not gated. Flagging rather than fixing, since it is unrelated and `bot/` is excluded from the formatter by project convention. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(bot): a reaction that never reached the backend now says so (#409)
All checks were successful
CI / Backend lint (ruff) (pull_request) Successful in 47s
CI / Frontend tests, audit, and build (pull_request) Successful in 1m30s
CI / Docker image build (pull_request) Successful in 21s
CI / Bot/backend version sync (pull_request) Successful in 43s
CI / Bot tests and audit (pull_request) Successful in 1m23s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m48s
CI / Backend migration, tests, and audit (pull_request) Successful in 5m11s
6cb9b4a0ae
Every failure in the reaction handlers was a log.warning and nothing else.
The emoji stays on the message either way, so a player whose vote never
reached Quest Board had every reason to believe it landed — and
auto_close_voting then confirms a time from the votes that did arrive, with
nothing to indicate the tally was short. A backend blip during a voting
window can decide when the group plays.

Failures now DM the reacting player: what did not happen, and what to do
about it while it still matters. DM first because the failure is theirs and
a channel-wide notice on a shared poll would confuse everyone else; an  on
the message is the fallback when DMs are closed, which is worse but beats
the silence this replaces. Success stays silent — a DM per vote would be
unusable.

Applies to votes, RSVPs and summary feedback.

Second half: a missing message mapping used to share one bare `return` with
"not one of our messages". A *tracked* emoji on a message this bot posted
is a different thing — a poll whose Redis mapping expired, or a restart
while on the in-memory fallback — and it now says so, with a different
remedy, because nothing will make that poll work again.

The discriminator is deliberately narrow: bot-authored AND one of our
emojis. The bot posts reminders and summaries too, and a 🎉 on a reminder
must not produce a "your vote failed" DM — the message only works if it is
rare. message_author_id is unset on reaction *remove*, so un-voting against
a lost mapping stays silent; removing a vote that was never recorded
changes nothing, and guessing would mean DMing anyone who reacts to
anything.

Mutation-checked: with the notifications disabled, 5 of the 8 new tests
fail. Three of them assert silence — success, an unrelated emoji, someone
else's message — so the change cannot pass by notifying indiscriminately.

228 bot tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
claude-bot deleted branch fix/409-visible-vote-failures 2026-08-31 05:11:37 +00:00
Sign in to join this conversation.
No description provided.