#20009: fix(discord): immediately defer interactions to prevent timeouts
channel: discord
size: S
Cluster:
Discord and MS Teams Fixes
## Summary
- Problem: Discord component interactions (buttons, select menus, modals) time out with "This component has expired" because `interaction.defer()` is called AFTER slow DM auth checks and session store lookups, exceeding Discord's 3-second ACK window.
- Why it matters: Users see broken "component has expired" errors and interactions fail silently, degrading the Discord experience.
- What changed: Moved `interaction.defer()` / `interaction.acknowledge()` to be the very first async operation in all component interaction handlers in [agent-components.ts], before any auth, allowlist, or session store checks.
- What did NOT change (scope boundary): No changes to slash command handling (native-command.ts) , exec approval buttons (exec-approvals.ts), message listeners, or any business logic — only the ordering of the ACK call within existing handlers.
## Change Type (select all)
- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Docs
- [ ] Security hardening
- [ ] Chore/infra
## Scope (select all touched areas)
- [ ] Gateway / orchestration
- [ ] Skills / tool execution
- [ ] Auth / tokens
- [ ] Memory / storage
- [x] Integrations
- [ ] API / contracts
- [ ] UI / DX
- [ ] CI/CD / infra
## Linked Issue/PR
- Closes #19909
## User-visible / Behavior Changes
- Discord component interactions (buttons, select menus, form submissions) no longer show "This component has expired" when the server is under load or auth checks are slow.
- No config changes or new defaults.
## Security Impact (required)
- New permissions/capabilities? [No]
- Secrets/tokens handling changed? [No]
- New/changed network calls? [No]
- Command/tool execution surface changed? [No]
- Data access scope changed? [No]
## Repro + Verification
### Environment
- OS: Windows
- Runtime/container: Node.js
- Model/provider: N/A
- Integration/channel (if any): Discord
- Relevant config (redacted): Default Discord config with agent components enabled
### Steps
1. Trigger a Discord component interaction (click a button or select menu) when the server has moderate load or DM auth checks are slow (>3s).
2. Observe that the interaction no longer times out with "This component has expired".
3. Run `npx vitest run src/discord/monitor.test.ts` — all 53 tests pass.
### Expected
- Interaction is acknowledged within 3 seconds; user sees the expected response.
### Actual
- Before fix: "This component has expired" error after ~5.6s (`Slow listener detected: InteractionEventListener took 5642ms`).
- After fix: Interaction is deferred immediately; processing continues in the background.
## Evidence
Attach at least one:
- [x] Failing test/log before + passing after
- [x] Trace/log snippets
- [ ] Screenshot/recording
- [ ] Perf numbers (if relevant)
Log before fix:
[EventQueue] Slow listener detected: InteractionEventListener took 5642ms for event INTERACTION_CREATE
Test results after fix:
✓ src/discord/monitor.test.ts (53 tests) 57ms Test Files 1 passed (1) Tests 53 passed (53)
## Human Verification (required)
- Verified scenarios: All 53 existing Discord monitor tests pass after the change.
- Edge cases checked: Modal submissions use `acknowledge()` instead of `defer()`; handlers that set `defer: false` (modal triggers) are correctly skipped; error replies use correct `replyOpts` after deferral.
- What you did **not** verify: Live Discord bot testing under real network latency (requires deployment).
## Compatibility / Migration
- Backward compatible? (Yes)
- Config/env changes? [No]
- Migration needed? [No]
## Failure Recovery (if this breaks)
- How to disable/revert this change quickly: Revert the changes — single file change in [agent-components.ts]
- Files/config to restore: [src/discord/monitor/agent-components.ts]
- Known bad symptoms reviewers should watch for: Double-reply errors if Discord receives two ACKs, or ephemeral flag mismatch on deferred responses.
## Risks and Mitigations
- Risk: If `defer()` itself throws unexpectedly, subsequent `interaction.reply()` calls may fail since the interaction was never acknowledged.
- Mitigation: The existing `try/catch` around `defer()` logs the error and falls back to setting `replyOpts.ephemeral = true`, so replies still attempt to go through.
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR fixes Discord interaction timeouts by reordering async operations to ensure `defer()` / `acknowledge()` is called as the first async operation in component interaction handlers, before slow DM auth checks and session store lookups that can exceed Discord's 3-second ACK window.
- `handleDiscordComponentEvent`, `AgentComponentButton.run`, `AgentSelectMenu.run`: `resolveInteractionContextWithDmAuth` (which internally calls `defer()`) is moved before component data parsing, ensuring the interaction is deferred within Discord's 3-second window.
- `DiscordComponentModal.run`: `interaction.acknowledge()` is moved to the very top of the handler before any validation or auth checks.
- `handleDiscordModalTrigger`: `resolveInteractionContextWithDmAuth` is moved earlier for consistency, though this path uses `defer: false` (since modals require `showModal()` as the response), so it does not benefit from the timeout fix.
- Error reply paths changed from hardcoded `ephemeral: true` to `...replyOpts` spread, which correctly adapts based on whether the interaction was deferred.
<h3>Confidence Score: 4/5</h3>
- This PR is safe to merge — it correctly fixes Discord interaction timeouts by reordering existing async operations, with no changes to business logic.
- The change is a straightforward reordering of existing function calls to ensure Discord interactions are acknowledged within the 3-second window. The logic and control flow remain the same. All 53 tests pass. Minor concerns are non-blocking: unnecessary DM auth work in the modal trigger path for invalid components, and ephemeral flags on reply calls after acknowledge() in DiscordComponentModal that have no practical effect.
- No files require special attention — the changes are confined to operation ordering within `src/discord/monitor/agent-components.ts`.
<sub>Last reviewed commit: 6e49be0</sub>
<!-- greptile_other_comments_section -->
<sub>(4/5) You can add custom instructions or style guidelines for the agent [here](https://app.greptile.com/review/github)!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#21463: fix(discord): prevent WebSocket death spiral + fix numeric channel ID…
by akropp · 2026-02-20
80.9%
#17254: fix(discord): intercept text-based slash commands instead of forwar...
by robbyczgw-cla · 2026-02-15
80.9%
#23158: discord: harden preflight/reply path against slow lookup latency
by danielstarman · 2026-02-22
80.1%
#22557: fix(discord): coerce exec approval approver IDs to string to preven...
by zwffff · 2026-02-21
79.8%
#15900: fix(discord): filter bot's own messages early to prevent self-DoS
by Shuai-DaiDai · 2026-02-14
78.2%
#19917: feat(discord): allow disabling intermediate status reactions
by Gitjay11 · 2026-02-18
77.6%
#16736: fix: stagger multi-account channel startup to avoid Discord rate li...
by rm289 · 2026-02-15
77.6%
#20913: fix: intercept Discord embed images to enforce mediaMaxMb
by MumuTW · 2026-02-19
77.3%
#19615: fix(discord): include default account when sub-accounts are configured
by prue-starfield · 2026-02-18
76.9%
#17316: fix: ack reaction not removed when block streaming is enabled (Tele...
by czmathew · 2026-02-15
76.4%