Relationships
#278 discord-bot double-sends sign_up notifications
Opened by keeb · 5/7/2026· Shipped 5/7/2026
Symptom
Even with the discord-bot scaled to 1 node, sign-up notifications are sometimes posted to the Discord channel twice. Other event types (extension_published, release_published, badge_awarded) are not observed to duplicate.
Why dedup doesn't catch it
discord_event_queue is keyed off _id: event.insert_id and DiscordConsumer.publish() swallows code-11000 dup-key errors (services/telemetry/lib/consumers/discord.ts:42-53).
But insert_id is generated fresh on every /ingest call by the telemetry server:
// services/telemetry/lib/schema.ts:189
insert_id: crypto.randomUUID(),So it dedupes a re-insert of the same telemetry doc, not two telemetry docs that describe the same logical sign-up. Once the discord-bot has processed and deleteOne'd the queue doc, a re-publish with the same insert_id succeeds (no dup error) and a second embed is sent.
The mechanism that re-inserts
services/telemetry/lib/watcher.ts:dispatchBatch runs:
- mark batch
processing - call
DiscordConsumer.publish()-> inserts intodiscord_event_queue - set
consumer_status.discord = "delivered" - await
onBatchDelivered deleteMany
If the watcher dies between steps 2 and 5 (deploy, OOM, SIGTERM), the events-collection doc is stuck in processing even though discord already received it. On next startup:
recoverOrphaned(watcher.ts:245-263) resets everyprocessingdoc older than 5 min back topendingwithout consultingconsumer_status.- The next
processPendingdispatches toregistry.names(watcher.ts:113) — all consumers, not just the ones that still need to run. DiscordConsumer.publish()runs again. Since the bot already drained and deleted the original queue doc,insertOne(_id: insert_id)succeeds with no dup error. A new queue doc lands, the bot polls it, posts again -> duplicate.
retryFailed has the same shape: if publish() partially succeeds and then throws on a transient mongo error mid-loop, the whole batch is re-dispatched and any event the bot has already drained gets re-inserted.
Why specifically sign_up
Of the four discord-relevant events, sign_up is the only one that fires on routine traffic. extension_published / release_published / badge_awarded are bursty/rare. Sign-ups are statistically the events most likely to be mid-pipeline when a deploy or watcher restart happens, so they're the ones that hit this race in practice.
Suggested fixes (one or more)
- Stable dedup key for sign_up.
track("sign_up", user.id, ...)only fires once per user creation inlib/auth.ts:464. Key the queue byevent + distinct_id(e.g._id: "sign_up:" + distinct_id) so a re-publish hits 11000 even after the bot has drained the original. Smallest, most targeted fix. - Consumer-status-aware dispatch.
processPendingandrecoverOrphanedshould skip consumers already markeddeliveredfor a doc, so re-running the pipeline after a crash doesn't re-call discord at all. Fixes the class of bug, not just sign_up. - Ack-then-delete. Mark the events-collection doc
delivered(or delete it) immediately after the consumer succeeds, instead of relying on thedeleteManyat the end of the batch.
Reproduction (theoretical)
- Send a
sign_upevent to/ingest. - Once
DiscordConsumer.publish()has inserted intodiscord_event_queuebut before watcher'sdeleteMany, kill the telemetry watcher. - Wait for the bot to drain the queue doc and delete it.
- Wait >5 min and restart the watcher;
recoverOrphanedresets the events doc topending,processPendingre-dispatches to all consumers, discord queue is re-populated, bot posts again.
Environment
- swamp-club main, observed in production with discord-bot scaled to 1 node.
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.