Skip to content

[Bug]: Message handlers block indefinitely due to fire-and-forget pattern + lane queue deadlock #76955

@rogerfsh

Description

@rogerfsh

Summary

OpenClaw's Discord integration (and potentially all inbound message handlers) silently deadlocks when receiving messages. The gateway accepts messages from Discord, triggers typing indicators, but never processes them. This blocks the lane queue permanently, preventing any messages from being handled until gateway restart.

The root cause is a fire-and-forget promise pattern in message listeners combined with lane queue design that lacks task-level timeouts.

Steps to reproduce

  1. Run OpenClaw 2026.5.2 with Discord plugin enabled
  2. Send a DM or channel mention to the bot
  3. Observe: typing indicator fires, auto-thread created, but no response
  4. Check logs: [diagnostic] lane wait exceeded: lane=session:agent:main:discord:channel:<ID> waitedMs=30000+ queueAhead=0

Note: This also affects standard chat/DM channels (not Discord-specific).

Expected behavior

Messages should be processed and responses delivered within a few seconds.

Actual behavior

  • ✅ Discord.js client receives message (MESSAGE_CREATE event fires)
  • ✅ Typing indicator sent to Discord
  • ✅ Auto-thread created in Discord
  • ❌ Message handler never executes or hangs
  • ❌ Lane queue blocks indefinitely with queueAhead=0 (no queue; active task stuck)
  • ❌ No error logged; handler failure is silently swallowed
  • ❌ Gateway remains responsive but Discord messages never complete

Environment

  • OpenClaw: 2026.5.2
  • Node.js: 22.22.0
  • Platform: macOS arm64
  • Plugins: bonjour, browser, device-pair, discord, file-transfer, memory-core, phone-control, talk-voice

Root Cause Analysis

File: ~/.openclaw/npm/node_modules/@openclaw/discord/src/monitor/listeners.ts lines 40-49

The Discord message listener uses fire-and-forget pattern:

export class DiscordMessageListener extends MessageCreateListener {
  async handle(data: DiscordMessageEvent, client: Client) {
    this.onEvent?.();
    void Promise.resolve()
      .then(() => this.handler(data, client))  // NEVER AWAITED
      .catch((err) => { logger.error(...); });
  }
}

Problem: The handler runs in background without awaiting. When handler hangs:

  • Discord.js returns immediately
  • Promise never settles
  • Lane queue's activeTaskIds.size never decrements
  • Queue refuses to process next message (maxConcurrent = 1)
  • Diagnostic log shows: lane wait exceeded: queueAhead=0

Proposed Fix

Await the handler with proper error handling:

async handle(data: DiscordMessageEvent, client: Client) {
  this.onEvent?.();
  try {
    await this.handler(data, client);
  } catch (err) {
    const logger = this.logger ?? discordEventQueueLog;
    logger.error(danger(`discord handler failed: ${String(err)}`));
  }
}

Related Issues

Severity

Critical — Blocks all inbound message handling. Single stalled handler message makes gateway unusable until restart.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions