Skip to content

Bug: Discord ack reaction silently fails with named accounts (no "default" account) #22938

@chancheuklap

Description

@chancheuklap

Environment

  • OpenClaw version: 2026.2.21-2
  • OS: macOS Darwin 25.3.0 (arm64)
  • Setup: Multi-account Discord config with named accounts only (coach_claw, cheuk_claw, info_claw), no "default" account

Description

When using named Discord accounts (i.e. all tokens are under channels.discord.accounts.<name>.token with no root channels.discord.token), ack reactions never appear and the failure is completely silent at debug log level — it only surfaces with --verbose.

Root Cause

In processDiscordMessage, the statusReactionController adapter calls reactMessageDiscord without passing accountId:

// src/discord/monitor/message-handler.process.ts (simplified)
adapter: {
  setReaction: async (emoji) => {
    await reactMessageDiscord(messageChannelId, message.id, emoji, {
      rest: client.rest
      // ⚠️ accountId is never passed here
    })
  }
}

This causes createDiscordRestClient to fall back to accountId = "default":

reactMessageDiscord({ rest: client.rest })  // no accountId
  → resolveDiscordAccount(undefined)
  → normalizeAccountId(undefined) → "default"
  → channels.discord.accounts["default"] → undefined
  → channels.discord.token → undefined (not set)
  → DISCORD_BOT_TOKEN env var → undefined
  → token = ""
  → resolveToken throws: "Discord bot token missing for account 'default'"

The error is caught in applyEmoji's try/catch and forwarded to onError, which logs via logVerboseonly visible with --verbose flag, invisible at debug level.

Verbose log output:

discord ack cleanup failed target=<channelId>/<messageId>: Error: Discord bot token missing for account "default"

Why It's Hard to Diagnose

  1. No error is visible in standard debug logs — requires --verbose gateway startup flag
  2. The agent processes messages and responds normally (the WS/inbound path is unaffected)
  3. Config looks correct; ackReactionScope: "all" and per-account ackReaction emoji are both set

Workaround

Set channels.discord.token to any valid bot token. This satisfies the token resolver for the "default" fallback path. Crucially, since opts.rest is already provided by the caller, resolveRest(token, opts.rest) returns opts.rest directly — so the correct per-account REST client is still used, and the workaround works for all named accounts without per-account changes.

{
  channels: {
    discord: {
      token: "<any_valid_bot_token>",  // workaround: satisfies token resolver
      accounts: {
        coach_claw: { token: "...", ackReaction: "👨🏻‍🏫" },
        cheuk_claw: { token: "...", ackReaction: "🦞" },
        info_claw:  { token: "...", ackReaction: "🔍" }
      }
    }
  }
}

Suggested Fix

Pass accountId through to the reaction adapter in processDiscordMessage:

setReaction: async (emoji) => {
  await reactMessageDiscord(messageChannelId, message.id, emoji, {
    rest: client.rest,
    accountId  // ← add this
  })
},
removeReaction: async (emoji) => {
  await removeReactionDiscord(messageChannelId, message.id, emoji, {
    rest: client.rest,
    accountId  // ← add this
  })
}

This eliminates the need for the "default" account fallback entirely in this code path.

Metadata

Metadata

Assignees

No one assigned

    Labels

    staleMarked as stale due to inactivity

    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