From c16bc71279101be0995516754e3000b50696e945 Mon Sep 17 00:00:00 2001 From: Shadow Date: Sat, 14 Feb 2026 11:01:07 -0600 Subject: [PATCH] fix: add discord routing debug logging (#16202) (thanks @jayleekr) --- CHANGELOG.md | 1 + .../monitor/message-handler.preflight.ts | 29 +++++++++++++++++++ src/routing/resolve-route.ts | 28 ++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9944714392..d694e96f585 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai ### Changes - Sandbox: add `sandbox.browser.binds` to configure browser-container bind mounts separately from exec containers. (#16230) Thanks @seheepeak. +- Discord: add debug logging for message routing decisions to improve `--debug` tracing. (#16202) Thanks @jayleekr. ### Fixes diff --git a/src/discord/monitor/message-handler.preflight.ts b/src/discord/monitor/message-handler.preflight.ts index 98121fb660b..b992b6904a1 100644 --- a/src/discord/monitor/message-handler.preflight.ts +++ b/src/discord/monitor/message-handler.preflight.ts @@ -21,6 +21,7 @@ import { loadConfig } from "../../config/config.js"; import { logVerbose, shouldLogVerbose } from "../../globals.js"; import { recordChannelActivity } from "../../infra/channel-activity.js"; import { enqueueSystemEvent } from "../../infra/system-events.js"; +import { logDebug } from "../../logger.js"; import { getChildLogger } from "../../logging.js"; import { buildPairingReply } from "../../pairing/pairing-messages.js"; import { @@ -104,6 +105,9 @@ export async function preflightDiscordMessage( const channelInfo = await resolveDiscordChannelInfo(params.client, message.channelId); const isDirectMessage = channelInfo?.type === ChannelType.DM; const isGroupDm = channelInfo?.type === ChannelType.GroupDM; + logDebug( + `[discord-preflight] channelId=${message.channelId} guild_id=${params.data.guild_id} channelType=${channelInfo?.type} isGuild=${isGuildMessage} isDM=${isDirectMessage} isGroupDm=${isGroupDm}`, + ); if (isGroupDm && !params.groupDmEnabled) { logVerbose("discord: drop group dm (group dms disabled)"); @@ -262,12 +266,18 @@ export async function preflightDiscordMessage( guildEntries: params.guildEntries, }) : null; + logDebug( + `[discord-preflight] guild_id=${params.data.guild_id} guild_obj=${!!params.data.guild} guild_obj_id=${params.data.guild?.id} guildInfo=${!!guildInfo} guildEntries=${params.guildEntries ? Object.keys(params.guildEntries).join(",") : "none"}`, + ); if ( isGuildMessage && params.guildEntries && Object.keys(params.guildEntries).length > 0 && !guildInfo ) { + logDebug( + `[discord-preflight] guild blocked: guild_id=${params.data.guild_id} guildEntries keys=${Object.keys(params.guildEntries).join(",")}`, + ); logVerbose( `Blocked discord guild ${params.data.guild_id ?? "unknown"} (not in discord.guilds)`, ); @@ -305,7 +315,16 @@ export async function preflightDiscordMessage( }) : null; const channelMatchMeta = formatAllowlistMatchMeta(channelConfig); + if (shouldLogVerbose()) { + const channelConfigSummary = channelConfig + ? `allowed=${channelConfig.allowed} enabled=${channelConfig.enabled ?? "unset"} requireMention=${channelConfig.requireMention ?? "unset"} matchKey=${channelConfig.matchKey ?? "none"} matchSource=${channelConfig.matchSource ?? "none"} users=${channelConfig.users?.length ?? 0} roles=${channelConfig.roles?.length ?? 0} skills=${channelConfig.skills?.length ?? 0}` + : "none"; + logDebug( + `[discord-preflight] channelConfig=${channelConfigSummary} channelMatchMeta=${channelMatchMeta} channelId=${message.channelId}`, + ); + } if (isGuildMessage && channelConfig?.enabled === false) { + logDebug(`[discord-preflight] drop: channel disabled`); logVerbose( `Blocked discord channel ${message.channelId} (channel disabled, ${channelMatchMeta})`, ); @@ -351,12 +370,14 @@ export async function preflightDiscordMessage( } if (isGuildMessage && channelConfig?.allowed === false) { + logDebug(`[discord-preflight] drop: channelConfig.allowed===false`); logVerbose( `Blocked discord channel ${message.channelId} not in guild channel allowlist (${channelMatchMeta})`, ); return null; } if (isGuildMessage) { + logDebug(`[discord-preflight] pass: channel allowed`); logVerbose(`discord: allow channel ${message.channelId} (${channelMatchMeta})`); } @@ -519,8 +540,12 @@ export async function preflightDiscordMessage( commandAuthorized, }); const effectiveWasMentioned = mentionGate.effectiveWasMentioned; + logDebug( + `[discord-preflight] shouldRequireMention=${shouldRequireMention} mentionGate.shouldSkip=${mentionGate.shouldSkip} wasMentioned=${wasMentioned}`, + ); if (isGuildMessage && shouldRequireMention) { if (botId && mentionGate.shouldSkip) { + logDebug(`[discord-preflight] drop: no-mention`); logVerbose(`discord: drop guild message (mention required, botId=${botId})`); logger.info( { @@ -540,6 +565,7 @@ export async function preflightDiscordMessage( } if (isGuildMessage && hasAccessRestrictions && !memberAllowed) { + logDebug(`[discord-preflight] drop: member not allowed`); logVerbose(`Blocked discord guild sender ${sender.id} (not in users/roles allowlist)`); return null; } @@ -552,6 +578,7 @@ export async function preflightDiscordMessage( }); const systemText = resolveDiscordSystemEvent(message, systemLocation); if (systemText) { + logDebug(`[discord-preflight] drop: system event`); enqueueSystemEvent(systemText, { sessionKey: route.sessionKey, contextKey: `discord:system:${message.channelId}:${message.id}`, @@ -560,10 +587,12 @@ export async function preflightDiscordMessage( } if (!messageText) { + logDebug(`[discord-preflight] drop: empty content`); logVerbose(`discord: drop message ${message.id} (empty content)`); return null; } + logDebug(`[discord-preflight] success: route=${route.agentId} sessionKey=${route.sessionKey}`); return { cfg: params.cfg, discordConfig: params.discordConfig, diff --git a/src/routing/resolve-route.ts b/src/routing/resolve-route.ts index 2b1ac338e3a..5cfa2bc6417 100644 --- a/src/routing/resolve-route.ts +++ b/src/routing/resolve-route.ts @@ -2,6 +2,8 @@ import type { ChatType } from "../channels/chat-type.js"; import type { OpenClawConfig } from "../config/config.js"; import { resolveDefaultAgentId } from "../agents/agent-scope.js"; import { normalizeChatType } from "../channels/chat-type.js"; +import { shouldLogVerbose } from "../globals.js"; +import { logDebug } from "../logger.js"; import { listBindings } from "./bindings.js"; import { buildAgentMainSessionKey, @@ -322,6 +324,29 @@ export function resolveAgentRoute(input: ResolveAgentRouteInput): ResolvedAgentR }; }; + const shouldLogDebug = shouldLogVerbose(); + const formatPeer = (value?: RoutePeer | null) => + value?.kind && value?.id ? `${value.kind}:${value.id}` : "none"; + const formatNormalizedPeer = (value: NormalizedPeerConstraint) => { + if (value.state === "none") { + return "none"; + } + if (value.state === "invalid") { + return "invalid"; + } + return `${value.kind}:${value.id}`; + }; + + if (shouldLogDebug) { + logDebug( + `[routing] resolveAgentRoute: channel=${channel} accountId=${accountId} peer=${formatPeer(peer)} guildId=${guildId || "none"} teamId=${teamId || "none"} bindings=${bindings.length}`, + ); + for (const entry of bindings) { + logDebug( + `[routing] binding: agentId=${entry.binding.agentId} accountPattern=${entry.match.accountPattern || "default"} peer=${formatNormalizedPeer(entry.match.peer)} guildId=${entry.match.guildId ?? "none"} teamId=${entry.match.teamId ?? "none"} roles=${entry.match.roles?.length ?? 0}`, + ); + } + } // Thread parent inheritance: if peer (thread) didn't match, check parent peer binding const parentPeer = input.parentPeer ? { kind: input.parentPeer.kind, id: normalizeId(input.parentPeer.id) } @@ -397,6 +422,9 @@ export function resolveAgentRoute(input: ResolveAgentRouteInput): ResolvedAgentR }), ); if (matched) { + if (shouldLogDebug) { + logDebug(`[routing] match: matchedBy=${tier.matchedBy} agentId=${matched.binding.agentId}`); + } return choose(matched.binding.agentId, tier.matchedBy); } }