fix(discord): role-based allowlist never matches (Carbon Role objects stringify to mentions) (#16369)

* fix(discord): role-based allowlist never matches because Carbon Role objects stringify to mentions

Carbon's GuildMember.roles getter returns Role[] objects, not raw ID strings.
String(Role) produces '<@&123456>' which never matches the plain role IDs
in the guild allowlist config.

Use data.rawMember.roles (raw Discord API string array) instead of
data.member.roles (Carbon Role[] objects) for role ID extraction.

Fixes #16207

* Docs: add discord role allowlist changelog entry

---------

Co-authored-by: Shadow <hi@shadowing.dev>
This commit is contained in:
Xinhua Gu
2026-02-15 20:05:46 +01:00
committed by GitHub
parent c7b6d6a14e
commit c682634188
3 changed files with 5 additions and 4 deletions

View File

@@ -33,6 +33,7 @@ Docs: https://docs.openclaw.ai
- Telegram: finalize streaming preview replies in place instead of sending a second final message, preventing duplicate Telegram assistant outputs at stream completion. (#17218) Thanks @obviyus.
- Cron: infer `payload.kind="agentTurn"` for model-only `cron.update` payload patches, so partial agent-turn updates do not fail validation when `kind` is omitted. (#15664) Thanks @rodrigouroz.
- Subagents: use child-run-based deterministic announce idempotency keys across direct and queued delivery paths (with legacy queued-item fallback) to prevent duplicate announce retries without collapsing distinct same-millisecond announces. (#17150) Thanks @widingmarcus-cyber.
- Discord: ensure role allowlist matching uses raw role IDs for message routing authorization. Thanks @xinhuagu.
## 2026.2.14

View File

@@ -272,8 +272,8 @@ async function handleDiscordReactionEvent(params: {
const authorLabel = message?.author ? formatDiscordUserTag(message.author) : undefined;
const baseText = `Discord reaction ${action}: ${emojiLabel} by ${actorLabel} on ${guildSlug} ${channelLabel} msg ${data.message_id}`;
const text = authorLabel ? `${baseText} from ${authorLabel}` : baseText;
const memberRoleIds = Array.isArray(data.member?.roles)
? data.member.roles.map((roleId: string) => String(roleId))
const memberRoleIds = Array.isArray(data.rawMember?.roles)
? data.rawMember.roles.map((roleId: string) => String(roleId))
: [];
const route = resolveAgentRoute({
cfg: params.cfg,

View File

@@ -224,8 +224,8 @@ export async function preflightDiscordMessage(
}
// Fresh config for bindings lookup; other routing inputs are payload-derived.
const memberRoleIds = Array.isArray(params.data.member?.roles)
? params.data.member.roles.map((roleId: string) => String(roleId))
const memberRoleIds = Array.isArray(params.data.rawMember?.roles)
? params.data.rawMember.roles.map((roleId: string) => String(roleId))
: [];
const route = resolveAgentRoute({
cfg: loadConfig(),