Repository navigation
Handle refused Discord acknowledgements once, and tell 10062 from 40060 - #139
Merged
Merged
Conversation
A deferral that Discord refused had no handler until the work finished, so Node raised an unhandled rejection (logged and sent to Sentry with no actor) before the adapter logged it again. A refused placeholder reply was caught by the dispatcher as command.failed and captured. The deferral now has a no-op catch at creation (still awaited later), and an onPending failure is rethrown by the dispatcher unreported, so the adapter handles it once. The handler still doesn't run. Only 40060 is now treated as a second instance (discord.duplicate_handler). 10062 means Pixel acknowledged too slowly: it is logged as discord.slow_ack with the command and actor, and reported as a SlowAcknowledgementError naming the command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
1. Double-reported deferral failures, and placeholder failures reported as command failures.
When a command takes more than 1.5 s,
respond()callsdeferReply(). If Discord refuses it (40060 already acknowledged, or 10062 unknown interaction), that promise had no handler until the work finished, so Node emittedunhandledRejection: process logging logged it as an error and Sentry captured it with no actor or command, and then the adapter logged it a second time asdiscord.duplicate_handler.Separately, for commands with a placeholder (
/status,/ha, admin roles, ...), a refused placeholderinteraction.replyinsideonPendingwas caught by the dispatcher ascommand.failed(error log,reporter.capture, error metric), defeating the adapter's warn-only branch.2. Every 10062 blamed on a second instance.
10062 also happens when Pixel itself is slow to acknowledge (e.g. the form path can't defer before
showModal, andbeforeFormdoes Discord REST work), but it was always logged as a warning about a second instance and never reported.What changed
respond.ts: the deferral promise gets a no-op.catchwhen it's created. It's still awaited afterwards, so its failure surfaces once, fromrespond().dispatcher.ts(small change): ifonPendingthrows, the dispatcher rethrows that error without loggingcommand.failedor capturing it, and the handler still doesn't run. The adapter now decides how to log and report it. TheDispatchHooks.onPendingdoc says so. I couldn't do this in the adapter alone: anything that keeps the handler from running has to throw out ofonPending, and the dispatcher's catch captured every such throw. The change is indispatch()only, not the form path.interaction-errors.ts:classifyInteractionErrorreturnsduplicate(40060),expired(10062) orother.handleInteractionFailureis the one place the adapter logs these failures, and both the command and modal paths inindex.tsuse it:discord.duplicate_handler(second instance), not reported.discord.slow_ackwithcommandandactorLogFields, and reports aSlowAcknowledgementError("Discord expired /<command> before Pixel acknowledged it", { cause })with the actor, so Sentry groups it by command. Nothing typed by the user is logged.vitest.config.ts:interaction-errors.tsis added to the strict-coverage adapter files (a tighter floor, nothing lowered).discord.slow_ack/ "The application did not respond". CHANGELOGFixedentries added.Tests
discord.test.ts: adeferReplythat rejects with 10062 while the work is still running causes nounhandledRejection(checked with a realsetImmediateturn under fake timers), andrespond()rejects once with that error. This test fails without the.catch. A refused placeholder reply makesrespond()reject with that error.dispatcher.test.ts: whenonPendingrejects,dispatchrejects with the same error, the handler doesn't run, and there's nocaptureand nocommand.failed. A handler failure after a placeholder is still captured.interaction-errors.test.ts: classification, and log/report behaviour for 40060, 10062 and other errors, for commands and forms.just checkpasses.Needs a human reviewer (AGENTS.md rule 14):
src/core/dispatcher.tschanged. Another open PR edits the dispatcher's form path (prepareForm). This PR only touchesdispatch()aroundonPending, so a conflict, if any, should be trivial.🤖 Generated with Claude Code