Summary
In `handleDiscordReactionEvent`, `client.fetchChannel(data.channel_id)` is called unconditionally at line 475 — before any `reactionMode` check. When the Discord gateway disconnects and reconnects, queued `MESSAGE_REACTION_ADD` events fire simultaneously, all calling `fetchChannel` at once. This hits Discord REST rate limits and each call takes 30–73s before timing out, triggering cascading `Slow listener detected` warnings and session lock congestion.
This is distinct from #47516 (which covers unconditional listener registration when `reactionNotifications: "off"`). This bug affects any config using `reactionNotifications: "allowlist"` or `"all"`.
Environment
- OpenClaw: 2026.4.25
- OS: Ubuntu 24.04 (linux/amd64)
- Node: v22
- Discord: 2 guilds, both `reactionNotifications: "allowlist"`
- Multi-agent setup (~4 active sessions)
Root Cause
In `dist/extensions/discord/provider-C7FYFXVp.js`:
```js
async function handleDiscordReactionEvent(params) {
// ...
if (isGuildMessage && guildEntries && Object.keys(guildEntries).length > 0 && !guildInfo) return; // line 474 — last cheap exit
const channel = await client.fetchChannel(data.channel_id); // line 475 — REST call fires unconditionally
// ...
// reactionMode check only happens much later inside handleDiscordThreadReactionNotification / handleDiscordChannelReactionNotification
}
```
The `reactionMode === "off"` early-exit lives inside the downstream handlers (`handleDiscordThreadReactionNotification` line 417, `handleDiscordChannelReactionNotification` line 442) — but `fetchChannel` runs before reaching them for all reaction modes, including `"allowlist"`.
Reproduction
- Configure 2+ guilds with `reactionNotifications: "allowlist"`
- Let the Discord gateway disconnect (network blip, timeout, etc.)
- Several reaction events queue up during the ~30-40s reconnect window
- On reconnect, all queued events fire simultaneously
- Each calls `fetchChannel` concurrently → Discord REST rate limits → each call stalls 30–73s
- `Slow listener detected: DiscordReactionListener durationMs=36000+` logged repeatedly
- Session write locks held past max (15s), sessions stall, auto-restart triggered
Log evidence
```
[warn] Slow listener detected: {"listener":"DiscordReactionListener","event":"MESSAGE_REACTION_ADD","durationMs":36184}
[warn] Slow listener detected: {"listener":"DiscordReactionListener","event":"MESSAGE_REACTION_ADD","durationMs":36271}
[warn] Slow listener detected: {"listener":"DiscordReactionListener","event":"MESSAGE_REACTION_ADD","durationMs":36300}
[warn] session-write-lock held for 91000ms (max=15000)
[info] [agent-name] auto-restart attempt 1/10
```
Suggested Fix
Move the `reactionMode` check to before the `fetchChannel` call:
```js
async function handleDiscordReactionEvent(params) {
// ... existing cheap checks ...
if (isGuildMessage && guildEntries && Object.keys(guildEntries).length > 0 && !guildInfo) return;
// Early exit before any REST call when reactions are disabled
const reactionMode = guildInfo?.reactionNotifications ?? "own";
if (reactionMode === "off") return;
const channel = await client.fetchChannel(data.channel_id); // now only called when reactions are enabled
// ...
}
```
For `"allowlist"` mode, it's also worth checking the user against the allowlist before calling `fetchChannel`, since the channel info isn't needed to make that determination.
Impact
- Every reaction event in `"allowlist"` or `"all"` mode makes a live Discord REST call
- After a gateway reconnect, burst of queued reactions causes concurrent `fetchChannel` calls → rate limit stalls → session lock cascade → agent auto-restarts
- Affects stability of multi-agent setups on Discord
Related
Summary
In `handleDiscordReactionEvent`, `client.fetchChannel(data.channel_id)` is called unconditionally at line 475 — before any `reactionMode` check. When the Discord gateway disconnects and reconnects, queued `MESSAGE_REACTION_ADD` events fire simultaneously, all calling `fetchChannel` at once. This hits Discord REST rate limits and each call takes 30–73s before timing out, triggering cascading `Slow listener detected` warnings and session lock congestion.
This is distinct from #47516 (which covers unconditional listener registration when `reactionNotifications: "off"`). This bug affects any config using `reactionNotifications: "allowlist"` or `"all"`.
Environment
Root Cause
In `dist/extensions/discord/provider-C7FYFXVp.js`:
```js
async function handleDiscordReactionEvent(params) {
// ...
if (isGuildMessage && guildEntries && Object.keys(guildEntries).length > 0 && !guildInfo) return; // line 474 — last cheap exit
const channel = await client.fetchChannel(data.channel_id); // line 475 — REST call fires unconditionally
// ...
// reactionMode check only happens much later inside handleDiscordThreadReactionNotification / handleDiscordChannelReactionNotification
}
```
The `reactionMode === "off"` early-exit lives inside the downstream handlers (`handleDiscordThreadReactionNotification` line 417, `handleDiscordChannelReactionNotification` line 442) — but `fetchChannel` runs before reaching them for all reaction modes, including `"allowlist"`.
Reproduction
Log evidence
```
[warn] Slow listener detected: {"listener":"DiscordReactionListener","event":"MESSAGE_REACTION_ADD","durationMs":36184}
[warn] Slow listener detected: {"listener":"DiscordReactionListener","event":"MESSAGE_REACTION_ADD","durationMs":36271}
[warn] Slow listener detected: {"listener":"DiscordReactionListener","event":"MESSAGE_REACTION_ADD","durationMs":36300}
[warn] session-write-lock held for 91000ms (max=15000)
[info] [agent-name] auto-restart attempt 1/10
```
Suggested Fix
Move the `reactionMode` check to before the `fetchChannel` call:
```js
async function handleDiscordReactionEvent(params) {
// ... existing cheap checks ...
if (isGuildMessage && guildEntries && Object.keys(guildEntries).length > 0 && !guildInfo) return;
// Early exit before any REST call when reactions are disabled
const reactionMode = guildInfo?.reactionNotifications ?? "own";
if (reactionMode === "off") return;
const channel = await client.fetchChannel(data.channel_id); // now only called when reactions are enabled
// ...
}
```
For `"allowlist"` mode, it's also worth checking the user against the allowlist before calling `fetchChannel`, since the channel info isn't needed to make that determination.
Impact
Related