mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:10:43 +00:00
fix(discord): read channel.parentId through safe accessor on partial thread channels
The Carbon `GuildThreadChannel.parentId` getter throws "Cannot access rawData on partial Channel" whenever Discord delivers a partial thread (for example when an interaction channel is unhydrated). The existing `"parentId" in channel` guard did not help because the `in` operator returns true for prototype getters without invoking them, so the read still crashed `/new` and similar slash commands, guild reactions, and the native model picker when invoked from inside a thread. Expose a `resolveDiscordChannelParentIdSafe` helper alongside the other channel accessors and use it everywhere we currently read `channel.parentId` from the inbound Discord channel. When the getter throws, the helper returns `undefined`, and the downstream code already falls back to re-fetching the thread id via `resolveDiscordChannelInfo`, keeping authorization/config lookups on the same inputs as before. Add a regression test that installs a throwing `parentId` getter on a partial guild thread channel and asserts the slash-command path still defers and dispatches instead of surfacing an unauthorized reply. Fixes #69861
This commit is contained in:
committed by
Peter Steinberger
parent
b0734664f8
commit
dbf8fd0db7
@@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Mattermost: suppress reasoning-only payloads even when they arrive as blockquoted `> Reasoning:` text, preventing `/reasoning on` from leaking thinking into channel posts. (#69927) Thanks @lawrence3699.
|
||||
- Discord: read `channel.parentId` through a safe accessor in the slash-command, reaction, and model-picker paths so partial `GuildThreadChannel` prototype getters no longer throw `Cannot access rawData on partial Channel` when commands like `/new` run from inside a thread. Fixes #69861. (#69908) Thanks @neeravmakwana.
|
||||
- Browser/Chrome MCP: reset cached existing-session control sessions when a `navigate_page` call times out, so one stuck navigation no longer poisons the browser profile until a gateway restart. (#69733) Thanks @ayeshakhalid192007-dev.
|
||||
- Browser/Chrome MCP: propagate click timeouts and abort signals to existing-session actions so a stuck click fails fast and reconnects instead of poisoning the browser tool until gateway restart. (#63524) Thanks @dongseok0.
|
||||
- OpenCode Go: canonicalize stale bundled `opencode-go` base URLs from `/go` or `/go/v1` to `/zen/go` or `/zen/go/v1`, so older generated model metadata stops hitting the 404 HTML endpoint. (#69898)
|
||||
|
||||
@@ -42,6 +42,10 @@ export function resolveDiscordChannelTopicSafe(channel: unknown): string | undef
|
||||
return resolveDiscordChannelStringPropertySafe(channel, "topic");
|
||||
}
|
||||
|
||||
export function resolveDiscordChannelParentIdSafe(channel: unknown): string | undefined {
|
||||
return resolveDiscordChannelStringPropertySafe(channel, "parentId");
|
||||
}
|
||||
|
||||
export function resolveDiscordChannelInfoSafe(channel: unknown): DiscordChannelInfoSafe {
|
||||
const parent = readDiscordChannelPropertySafe(channel, "parent");
|
||||
return {
|
||||
|
||||
@@ -32,7 +32,10 @@ import {
|
||||
resolveDiscordGuildEntry,
|
||||
shouldEmitDiscordReactionNotification,
|
||||
} from "./allow-list.js";
|
||||
import { resolveDiscordChannelInfoSafe } from "./channel-access.js";
|
||||
import {
|
||||
resolveDiscordChannelInfoSafe,
|
||||
resolveDiscordChannelParentIdSafe,
|
||||
} from "./channel-access.js";
|
||||
import { formatDiscordReactionEmoji, formatDiscordUserTag } from "./format.js";
|
||||
import { resolveDiscordChannelInfo } from "./message-utils.js";
|
||||
import { setPresence } from "./presence-cache.js";
|
||||
@@ -487,7 +490,7 @@ async function handleDiscordReactionEvent(
|
||||
return;
|
||||
}
|
||||
}
|
||||
let parentId = "parentId" in channel ? (channel.parentId ?? undefined) : undefined;
|
||||
let parentId = resolveDiscordChannelParentIdSafe(channel);
|
||||
let parentName: string | undefined;
|
||||
let parentSlug = "";
|
||||
let reactionBase: { baseText: string; contextKey: string } | null = null;
|
||||
|
||||
@@ -34,7 +34,10 @@ import {
|
||||
normalizeOptionalString,
|
||||
withTimeout,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveDiscordChannelNameSafe } from "./channel-access.js";
|
||||
import {
|
||||
resolveDiscordChannelNameSafe,
|
||||
resolveDiscordChannelParentIdSafe,
|
||||
} from "./channel-access.js";
|
||||
import { resolveDiscordSlashCommandConfig } from "./commands.js";
|
||||
import { resolveDiscordChannelInfo } from "./message-utils.js";
|
||||
import {
|
||||
@@ -258,7 +261,7 @@ async function resolveDiscordModelPickerRouteState(params: {
|
||||
threadChannel: {
|
||||
id: rawChannelId,
|
||||
name: resolveDiscordChannelNameSafe(channel),
|
||||
parentId: "parentId" in channel ? (channel.parentId ?? undefined) : undefined,
|
||||
parentId: resolveDiscordChannelParentIdSafe(channel),
|
||||
parent: undefined,
|
||||
},
|
||||
channelInfo,
|
||||
|
||||
@@ -192,6 +192,29 @@ describe("Discord native slash commands with commands.allowFrom", () => {
|
||||
expectNotUnauthorizedReply(interaction);
|
||||
});
|
||||
|
||||
it("tolerates partial guild thread channels whose parentId getter throws", async () => {
|
||||
const { dispatchSpy, interaction } = await runGuildSlashCommand({
|
||||
mutateInteraction: (currentInteraction) => {
|
||||
currentInteraction.channel = {
|
||||
type: ChannelType.PublicThread,
|
||||
id: currentInteraction.channel.id,
|
||||
} as MockCommandInteraction["channel"];
|
||||
Object.defineProperty(currentInteraction.channel, "parentId", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() {
|
||||
throw new Error(
|
||||
"Cannot access rawData on partial Channel. Use fetch() to populate data.",
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
expect(interaction.defer).toHaveBeenCalledTimes(1);
|
||||
expect(dispatchSpy).toHaveBeenCalledTimes(1);
|
||||
expectNotUnauthorizedReply(interaction);
|
||||
});
|
||||
|
||||
it("authorizes guild slash commands from an allowlisted channel when commands.allowFrom is not configured", async () => {
|
||||
const { dispatchSpy, interaction } = await runGuildSlashCommand({
|
||||
mutateConfig: (cfg) => {
|
||||
|
||||
@@ -65,7 +65,11 @@ import {
|
||||
resolveDiscordOwnerAccess,
|
||||
resolveGroupDmAllow,
|
||||
} from "./allow-list.js";
|
||||
import { resolveDiscordChannelNameSafe, resolveDiscordChannelTopicSafe } from "./channel-access.js";
|
||||
import {
|
||||
resolveDiscordChannelNameSafe,
|
||||
resolveDiscordChannelParentIdSafe,
|
||||
resolveDiscordChannelTopicSafe,
|
||||
} from "./channel-access.js";
|
||||
import { resolveDiscordDmCommandAccess } from "./dm-command-auth.js";
|
||||
import { handleDiscordDmCommandDecision } from "./dm-command-decision.js";
|
||||
import { resolveDiscordChannelInfo } from "./message-utils.js";
|
||||
@@ -466,7 +470,7 @@ async function resolveDiscordNativeAutocompleteAuthorized(params: {
|
||||
threadChannel: {
|
||||
id: rawChannelId,
|
||||
name: channelName,
|
||||
parentId: "parentId" in channel ? (channel.parentId ?? undefined) : undefined,
|
||||
parentId: resolveDiscordChannelParentIdSafe(channel),
|
||||
parent: undefined,
|
||||
},
|
||||
channelInfo,
|
||||
@@ -859,7 +863,7 @@ async function dispatchDiscordCommandInteraction(params: {
|
||||
threadChannel: {
|
||||
id: rawChannelId,
|
||||
name: channelName,
|
||||
parentId: "parentId" in channel ? (channel.parentId ?? undefined) : undefined,
|
||||
parentId: resolveDiscordChannelParentIdSafe(channel),
|
||||
parent: undefined,
|
||||
},
|
||||
channelInfo,
|
||||
@@ -1072,7 +1076,9 @@ async function dispatchDiscordCommandInteraction(params: {
|
||||
interaction.channel?.type === ChannelType.AnnouncementThread;
|
||||
const messageThreadId = !isDirectMessage && isThreadChannel ? channelId : undefined;
|
||||
const threadParentId =
|
||||
!isDirectMessage && isThreadChannel ? (interaction.channel.parentId ?? undefined) : undefined;
|
||||
!isDirectMessage && isThreadChannel
|
||||
? resolveDiscordChannelParentIdSafe(interaction.channel)
|
||||
: undefined;
|
||||
const { effectiveRoute } = await getNativeRouteState();
|
||||
const pluginReply = await executePluginCommandImpl({
|
||||
command: pluginMatch.command,
|
||||
|
||||
Reference in New Issue
Block a user