From bf6bd7432a67231a5018923d6ed7195e2ef412e7 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 4 Apr 2026 00:37:46 +0900 Subject: [PATCH] fix: harden discord ack auth and gate fallout (#60081) (thanks @FunJim) --- CHANGELOG.md | 1 + .../monitor/message-handler.process.test.ts | 41 +++++++++++++++++-- extensions/discord/src/subagent-hooks.ts | 4 +- extensions/telegram/src/approval-native.ts | 2 +- src/auto-reply/reply/commands.test.ts | 2 +- src/secrets/runtime.test.ts | 6 +-- 6 files changed, 44 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef248e9bd0f..c6d3a5c2f79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ Docs: https://docs.openclaw.ai - Plugins/allowlists: let explicit bundled chat channel enablement bypass `plugins.allow`, while keeping auto-enabled channel activation and startup sidecars behind restrictive allowlists. (#60233) Thanks @dorukardahan. - Allowlist/commands: require owner access for `/allowlist add` and `/allowlist remove` so command-authorized non-owners cannot mutate persisted allowlists. (#59836) Thanks @eleqtrizit. - Control UI/skills: clear stale ClawHub results immediately when the search query changes, so debounced searches cannot keep outdated install targets visible. Related #60134. +- Discord/ack reactions: keep automatic ACK reaction auth on the active hydrated Discord account so SecretRef-backed and non-default-account reactions stop falling back to stale default config resolution. (#60081) Thanks @FunJim. ## 2026.4.2 diff --git a/extensions/discord/src/monitor/message-handler.process.test.ts b/extensions/discord/src/monitor/message-handler.process.test.ts index d509832db77..68e851da26d 100644 --- a/extensions/discord/src/monitor/message-handler.process.test.ts +++ b/extensions/discord/src/monitor/message-handler.process.test.ts @@ -303,14 +303,31 @@ describe("processDiscordMessage ack reactions", () => { it("sends ack reactions for mention-gated guild messages when mentioned", async () => { const ctx = await createBaseContext({ + accountId: "ops", shouldRequireMention: true, effectiveWasMentioned: true, + route: { + agentId: "main", + channel: "discord", + accountId: "ops", + sessionKey: "agent:main:discord:channel:c1", + mainSessionKey: "agent:main:main", + }, }); // oxlint-disable-next-line typescript/no-explicit-any await processDiscordMessage(ctx as any); - expect(sendMocks.reactMessageDiscord.mock.calls[0]).toEqual(["c1", "m1", "👀", { rest: {}, cfg: expect.objectContaining({ messages: { ackReaction: "👀" } }) }]); + expect(sendMocks.reactMessageDiscord.mock.calls[0]).toEqual([ + "c1", + "m1", + "👀", + { + rest: {}, + cfg: expect.objectContaining({ messages: { ackReaction: "👀" } }), + accountId: "ops", + }, + ]); }); it("uses preflight-resolved messageChannelId when message.channelId is missing", async () => { @@ -332,7 +349,11 @@ describe("processDiscordMessage ack reactions", () => { "fallback-channel", "m1", "👀", - { rest: {}, cfg: expect.objectContaining({ messages: { ackReaction: "👀" } }) }, + { + rest: {}, + cfg: expect.objectContaining({ messages: { ackReaction: "👀" } }), + accountId: "default", + }, ]); }); @@ -488,7 +509,21 @@ describe("processDiscordMessage ack reactions", () => { // oxlint-disable-next-line typescript/no-explicit-any await processDiscordMessage(ctx as any); - expect(sendMocks.removeReactionDiscord).toHaveBeenCalledWith("c1", "m1", "👀", expect.objectContaining({ rest: {}, cfg: expect.objectContaining({ messages: expect.objectContaining({ ackReaction: "👀", removeAckAfterReply: true }) }) })); + expect(sendMocks.removeReactionDiscord).toHaveBeenCalledWith( + "c1", + "m1", + "👀", + expect.objectContaining({ + rest: {}, + cfg: expect.objectContaining({ + messages: expect.objectContaining({ + ackReaction: "👀", + removeAckAfterReply: true, + }), + }), + accountId: "default", + }), + ); }); it("removes the plain ack reaction when status reactions are disabled and removeAckAfterReply is enabled", async () => { diff --git a/extensions/discord/src/subagent-hooks.ts b/extensions/discord/src/subagent-hooks.ts index aff17ba13b3..f952cfc8000 100644 --- a/extensions/discord/src/subagent-hooks.ts +++ b/extensions/discord/src/subagent-hooks.ts @@ -26,14 +26,14 @@ type DiscordSubagentSpawningEvent = { threadId?: string | number; }; childSessionKey: string; - agentId?: string; + agentId: string; label?: string; }; type DiscordSubagentEndedEvent = { targetSessionKey: string; accountId?: string; - targetKind?: string; + targetKind?: ThreadBindingTargetKind; reason?: string; sendFarewell?: boolean; }; diff --git a/extensions/telegram/src/approval-native.ts b/extensions/telegram/src/approval-native.ts index 2a11cba52fa..8bd200a583c 100644 --- a/extensions/telegram/src/approval-native.ts +++ b/extensions/telegram/src/approval-native.ts @@ -119,7 +119,7 @@ const resolveTelegramApproveCommandBehavior: NonNullable< isTelegramExecApprovalAuthorizedSender({ cfg, accountId, senderId }) && !isTelegramExecApprovalApprover({ cfg, accountId, senderId }) ) { - return { kind: "ignore" }; + return undefined; } return { kind: "reply", diff --git a/src/auto-reply/reply/commands.test.ts b/src/auto-reply/reply/commands.test.ts index 5f874f6982f..875847517df 100644 --- a/src/auto-reply/reply/commands.test.ts +++ b/src/auto-reply/reply/commands.test.ts @@ -503,7 +503,7 @@ const telegramCommandTestPlugin: ChannelPlugin = { isTelegramExecApprovalAuthorizedSender({ cfg, accountId, senderId }) && !getTelegramExecApprovalApprovers({ cfg, accountId }).includes(senderId?.trim() ?? "") ) { - return { kind: "ignore" } as const; + return undefined; } return { kind: "reply", diff --git a/src/secrets/runtime.test.ts b/src/secrets/runtime.test.ts index db0c0145dee..32bcdcb03e9 100644 --- a/src/secrets/runtime.test.ts +++ b/src/secrets/runtime.test.ts @@ -1217,7 +1217,7 @@ describe("secrets runtime snapshot", () => { const ignoredInactiveWarnings = snapshot.warnings.filter( (warning) => warning.code === "SECRETS_REF_IGNORED_INACTIVE_SURFACE", ); - expect(ignoredInactiveWarnings).toHaveLength(10); + expect(ignoredInactiveWarnings).toHaveLength(6); expect(snapshot.warnings.map((warning) => warning.path)).toEqual( expect.arrayContaining([ "agents.defaults.memorySearch.remote.apiKey", @@ -1226,10 +1226,6 @@ describe("secrets runtime snapshot", () => { "channels.telegram.accounts.disabled.botToken", "plugins.entries.brave.config.webSearch.apiKey", "plugins.entries.google.config.webSearch.apiKey", - "plugins.entries.xai.config.webSearch.apiKey", - "plugins.entries.moonshot.config.webSearch.apiKey", - "plugins.entries.perplexity.config.webSearch.apiKey", - "plugins.entries.firecrawl.config.webSearch.apiKey", ]), ); });