Fix Discord pick notifications silently dropped during autodraft chains #57

Merged
chrisp merged 1 commit from fix/discord-pick-notification-queue into main 2026-05-27 16:41:08 +00:00
Owner

Summary

  • Per-league queue: replaced the single global notification queue with a `Map<leagueId, queue>` so concurrent drafts in different leagues drain independently and don't block each other
  • Deadlock fix: wrapped the drainer loop in `try/finally` so `drainingLeagues` is always cleaned up even if a task throws unexpectedly — previously a crash would leave the flag set permanently, silently dropping all future notifications for that league
  • Wait cap fix: moved `Math.min` to wrap `(retryAfter * (attempt+1))` so the 10s ceiling applies to the final product, not just the base value — settings actions that directly await webhook calls were previously blockable for up to 30s on a rate-limited third attempt

Root cause of the original problem

During an autodraft chain, `notifyPickMadeOnDiscord` was called fire-and-forget for each pick in the chain, causing all webhook requests to fly concurrently. Discord's per-webhook rate limit (~5 req/2s) rejected most of them; the single retry logic fired for all simultaneously, hit the limit again, and the `.catch()` swallowed the errors silently.

Test plan

  • Enable autodraft for 5+ consecutive teams; trigger the chain; confirm all picks announce in Discord in order
  • Confirm a slow/rate-limited webhook in League A does not delay League B's announcements
  • `npm run test:run` — 158 test files, 2336 tests, all pass

🤖 Generated with Claude Code

## Summary - **Per-league queue**: replaced the single global notification queue with a \`Map<leagueId, queue>\` so concurrent drafts in different leagues drain independently and don't block each other - **Deadlock fix**: wrapped the drainer loop in \`try/finally\` so \`drainingLeagues\` is always cleaned up even if a task throws unexpectedly — previously a crash would leave the flag set permanently, silently dropping all future notifications for that league - **Wait cap fix**: moved \`Math.min\` to wrap \`(retryAfter * (attempt+1))\` so the 10s ceiling applies to the final product, not just the base value — settings actions that directly await webhook calls were previously blockable for up to 30s on a rate-limited third attempt ## Root cause of the original problem During an autodraft chain, \`notifyPickMadeOnDiscord\` was called fire-and-forget for each pick in the chain, causing all webhook requests to fly concurrently. Discord's per-webhook rate limit (~5 req/2s) rejected most of them; the single retry logic fired for all simultaneously, hit the limit again, and the \`.catch()\` swallowed the errors silently. ## Test plan - [ ] Enable autodraft for 5+ consecutive teams; trigger the chain; confirm all picks announce in Discord in order - [ ] Confirm a slow/rate-limited webhook in League A does not delay League B's announcements - [ ] \`npm run test:run\` — 158 test files, 2336 tests, all pass 🤖 Generated with [Claude Code](https://claude.com/claude-code)
chrisp added 1 commit 2026-05-27 16:37:02 +00:00
Fix Discord pick notifications silently dropped during autodraft chains
All checks were successful
🚀 Deploy / 🧪 Test (pull_request) Successful in 1m29s
🚀 Deploy / ʦ TypeScript (pull_request) Successful in 1m20s
🚀 Deploy / 🔍 Lint (pull_request) Successful in 50s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped
fa85786401
Three issues identified by code review:

1. Per-league queue: replace single global queue with a Map keyed by leagueId
   so concurrent drafts in different leagues don't block each other.

2. Deadlock fix: wrap drainer body in try/finally so drainingLeagues is always
   cleaned up even if a task throws unexpectedly, preventing the queue from
   being permanently stuck.

3. Wait cap fix: move Math.min to wrap the full backoff expression
   (retryAfter * (attempt+1)) so the 10s ceiling applies to the product, not
   just the base — settings actions were previously blockable for up to 30s.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
chrisp merged commit 4a2f542fb1 into main 2026-05-27 16:41:08 +00:00
Sign in to join this conversation.
No description provided.