openclaw - ✅(Solved) Fix Discord slash commands intermittently fail with "This channel is not allowed" after gateway restart/reload due to duplicate InteractionEventListener [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#55070Fetched 2026-04-08 01:32:49
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Participants
Timeline (top)
cross-referenced ×1referenced ×1

Error Message

09:32:35 — SIGUSR1 received, gateway restarting 09:32:44 — Discord client initialized, awaiting gateway readiness 09:32:48 — [DEBUG] isDiscordGroupAllowedByPolicy → true (correctly allowed) 09:32:48 — DiscordError: Interaction has already been acknowledged. 09:32:49 — Slow listener: InteractionEventListener took 1945ms for INTERACTION_CREATE 09:32:58 — DiscordError: Interaction has already been acknowledged. (second attempt) 09:32:59 — Slow listener: InteractionEventListener took 2222ms for INTERACTION_CREATE

Root Cause

Root Cause (from debug investigation)

Fix Action

Workaround

Wait ~5 seconds after gateway restart before using slash commands.

PR fix notes

PR #55084: fix(discord): deactivate native command guard on abort to prevent stale interaction races

Description (problem / solution / changelog)

Summary

Fixes #55070.

During a gateway restart (SIGUSR1), the old Discord client's native command handlers could still process incoming interactions before the provider's finally block ran. This created a race window where both old and new clients processed the same INTERACTION_CREATE event, causing:

  • DiscordError: Interaction has already been acknowledged (code 40060)
  • Intermittent misleading "This channel is not allowed." responses

Root Cause

The existing nativeCommandsActive flag was only set to false in the finally block of monitorDiscordProvider(). However, between the abort signal firing and the finally block executing, there is a window where:

  1. waitForDiscordGatewayStop() is still resolving
  2. The old client's listeners are still registered and active
  3. nativeCommandsActive is still true, so the guard check passes
  4. Both old and new clients race to handle the same interaction

Fix

  • Add a guard parameter to createDiscordNativeCommand() with an isActive() check at the top of Command.run() so stale commands silently no-op
  • Register an abort listener that immediately sets nativeCommandsActive = false when the provider begins shutting down, closing the race window
  • Clean up the abort listener in the finally block
  • Keep the existing nativeCommandsActive = false in finally as a safety net

Evidence

Debug logging confirmed the allowlist check itself passes correctly (channelAllowed: true), but the interaction was processed twice:

09:32:48 — [DEBUG] isDiscordGroupAllowedByPolicy → true (correctly allowed)
09:32:48 — DiscordError: Interaction has already been acknowledged.
09:32:49 — Slow listener: InteractionEventListener took 1945ms

Testing

  • All existing lint, format, and type checks pass
  • The fix is minimal: 2 files changed, ~30 lines added
  • No behavioral change for normal (non-restart) operation

Changed files

  • extensions/discord/src/monitor/native-command.ts (modified, +9/-0)
  • extensions/discord/src/monitor/provider.ts (modified, +20/-0)

Code Example

isDiscordGroupAllowedByPolicy params: {
  "groupPolicy":"allowlist",
  "guildAllowlisted":true,
  "channelAllowlistConfigured":true,
  "channelAllowed":true
}
result: truePASSES

---

09:32:35SIGUSR1 received, gateway restarting
09:32:44Discord client initialized, awaiting gateway readiness
09:32:48[DEBUG] isDiscordGroupAllowedByPolicy → true (correctly allowed)
09:32:48DiscordError: Interaction has already been acknowledged.
09:32:49Slow listener: InteractionEventListener took 1945ms for INTERACTION_CREATE
09:32:58DiscordError: Interaction has already been acknowledged. (second attempt)
09:32:59Slow listener: InteractionEventListener took 2222ms for INTERACTION_CREATE
RAW_BUFFERClick to expand / collapse

Bug Description

After a gateway restart (SIGUSR1) or config hot-reload that triggers Discord channel restart, slash commands intermittently respond with "This channel is not allowed." even though the channel is correctly allowlisted.

Root Cause (from debug investigation)

The allowlist check itself passes correctly:

isDiscordGroupAllowedByPolicy params: {
  "groupPolicy":"allowlist",
  "guildAllowlisted":true,
  "channelAllowlistConfigured":true,
  "channelAllowed":true
}
result: true  ← PASSES

The real issue: During gateway restart/reload, the old Discord client's InteractionEventListener is not fully removed before the new client registers its own listener. This creates a brief window where the same INTERACTION_CREATE event is dispatched to two listeners:

  1. Listener A (old or new) successfully defers/responds to the interaction
  2. Listener B attempts to process the same interaction → gets DiscordError: Interaction has already been acknowledged (code 40060)

The user sees "This channel is not allowed." because the error-handling fallback path in the failed listener attempt produces this misleading response.

Evidence from Logs

09:32:35 — SIGUSR1 received, gateway restarting
09:32:44 — Discord client initialized, awaiting gateway readiness
09:32:48 — [DEBUG] isDiscordGroupAllowedByPolicy → true (correctly allowed)
09:32:48 — DiscordError: Interaction has already been acknowledged.
09:32:49 — Slow listener: InteractionEventListener took 1945ms for INTERACTION_CREATE
09:32:58 — DiscordError: Interaction has already been acknowledged. (second attempt)
09:32:59 — Slow listener: InteractionEventListener took 2222ms for INTERACTION_CREATE

Reproduction

  1. Configure groupPolicy: "allowlist" with channels properly allowlisted
  2. Trigger gateway restart via SIGUSR1 or config hot-reload
  3. Immediately invoke a slash command (within ~5 seconds of restart)
  4. Observe intermittent "This channel is not allowed." responses
  5. Retrying the same command succeeds once the old listener is fully gone

Environment

  • OpenClaw version: 2026.3.24
  • OS: macOS Darwin 25.3.0 (arm64)
  • Node: v24.13.0
  • Discord config: groupPolicy: "allowlist", channels configured by both name and ID

Expected Behavior

Old Discord client listeners should be fully deregistered/deactivated before the new client starts accepting interactions. Interactions received during the teardown window should be ignored by the old listener or queued for the new one.

Suggested Fix

In the Discord provider teardown (provider-CAlWEl41.js), ensure that:

  1. Old InteractionEventListener is explicitly removed before new client setup begins
  2. Or: Add a guard (e.g., generation counter / nonce) so only the current active listener processes interactions
  3. Or: Gate interaction processing on client.isReady() for the current generation

Workaround

Wait ~5 seconds after gateway restart before using slash commands.

Related Issues

  • #54681 (Discord /new slash command fails since Carbon reconcile — same Interaction has already been acknowledged errors)
  • #1122 (Thread allowlist — different root cause, same user-facing error message)

extent analysis

Fix Plan

To resolve the issue of intermittent "This channel is not allowed" responses after a gateway restart or config hot-reload, we need to ensure that the old Discord client's InteractionEventListener is fully removed before the new client registers its own listener. Here are the steps:

  • Explicitly remove the old InteractionEventListener: Modify the Discord provider teardown in provider-CAlWEl41.js to remove the old listener before setting up the new client.
  • Add a guard to prevent duplicate processing: Introduce a generation counter or nonce to ensure that only the current active listener processes interactions.
  • Gate interaction processing on client.isReady(): Only process interactions when the client is ready for the current generation.

Example code snippet to remove the old listener:

// provider-CAlWEl41.js
async function teardown() {
  // Remove the old InteractionEventListener
  const oldListener = this.listeners.get('INTERACTION_CREATE');
  if (oldListener) {
    this.client.removeListener('INTERACTION_CREATE', oldListener);
  }
  // ... rest of the teardown logic
}

Example code snippet to add a guard:

// InteractionEventListener.js
let currentGeneration = 0;

async function handleInteraction(interaction) {
  if (this.generation !== currentGeneration) {
    return; // Ignore interactions from previous generations
  }
  // Process the interaction
}

// When setting up a new client, increment the generation counter
currentGeneration++;

Verification

To verify that the fix worked:

  1. Trigger a gateway restart via SIGUSR1 or config hot-reload.
  2. Immediately invoke a slash command (within ~5 seconds of restart).
  3. Verify that the command responds correctly without displaying "This channel is not allowed."
  4. Repeat steps 1-3 multiple times to ensure the fix is consistent.

Extra Tips

  • Make sure to test the fix in different environments and scenarios to ensure it works as expected.
  • Consider adding logging to track the generation counter and listener removal to help with debugging.
  • Review related issues (#54681 and #1122) to ensure that the fix does not introduce any regressions.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING