fix(discord): strip channel: prefix in resolveChannelIdForBinding to fix thread_binding_invalid on ACP spawn#63354
Conversation
…ore Discord API call Fixes openclaw#63329 When spawning an ACP session with thread=true from a Discord guild channel, resolveChannelIdForBinding receives the conversation ID in OpenClaw's internal prefixed format (channel:1490136404385075452) and passes it directly to Routes.channel(). This causes Routes.channel() to URL-encode the colon, producing /channels/channel%3A1490136404385075452, which Discord rejects with an error. The error is swallowed silently, resolveChannelIdForBinding returns null, bindTarget returns null, and the spawn fails with thread_binding_invalid. Fix: strip the channel: or user: prefix from params.threadId before passing it to Routes.channel() so the Discord REST API receives a valid numeric ID.
Greptile SummarySingle-line regression fix in Confidence Score: 5/5Safe to merge — targeted one-line fix with no logic changes beyond stripping an internal ID prefix. The only finding is a P2 style suggestion to use an existing helper; the inline regex fix is functionally correct and directly addresses the reported regression. No blocking issues. No files require special attention.
|
| params.cfg, | ||
| ).rest; | ||
| const channel = (await rest.get(Routes.channel(params.threadId))) as { | ||
| const channel = (await rest.get(Routes.channel(params.threadId.replace(/^(channel:|user:)/i, '')))) as { |
There was a problem hiding this comment.
Consider using the existing
resolveDiscordChannelId helper
The file already imports helpers from elsewhere in the extension; the codebase has a purpose-built resolveDiscordChannelId in src/target-parsing.ts (re-exported via targets.ts) that already handles the channel: prefix-stripping correctly. Using it here would be more consistent with how the rest of the extension strips prefixes.
One caveat: resolveDiscordChannelId throws on a user: prefix (see its test), so the try/catch here would catch that and return null — effectively the same end result as the current inline regex for user-prefixed inputs. Either approach is correct for the stated regression; using the shared helper just keeps the stripping logic centralized.
| const channel = (await rest.get(Routes.channel(params.threadId.replace(/^(channel:|user:)/i, '')))) as { | |
| const channel = (await rest.get(Routes.channel(resolveDiscordChannelId(params.threadId)))) as { |
If you prefer the inline regex (to avoid importing and to tolerate user: targets silently), that's also fine — the fix is functionally correct for the channel: case.
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/discord/src/monitor/thread-bindings.discord-api.ts
Line: 248
Comment:
**Consider using the existing `resolveDiscordChannelId` helper**
The file already imports helpers from elsewhere in the extension; the codebase has a purpose-built `resolveDiscordChannelId` in `src/target-parsing.ts` (re-exported via `targets.ts`) that already handles the `channel:` prefix-stripping correctly. Using it here would be more consistent with how the rest of the extension strips prefixes.
One caveat: `resolveDiscordChannelId` throws on a `user:` prefix (see its test), so the try/catch here would catch that and return `null` — effectively the same end result as the current inline regex for user-prefixed inputs. Either approach is correct for the stated regression; using the shared helper just keeps the stripping logic centralized.
```suggestion
const channel = (await rest.get(Routes.channel(resolveDiscordChannelId(params.threadId)))) as {
```
If you prefer the inline regex (to avoid importing and to tolerate `user:` targets silently), that's also fine — the fix is functionally correct for the `channel:` case.
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Good call — updated to use resolveDiscordChannelId from target-parsing.ts in the latest commit. Agreed it's more consistent with the rest of the codebase.
Addresses code review suggestion from greptile-apps bot. Uses the existing resolveDiscordChannelId helper from target-parsing.ts which is the idiomatic way to strip channel:/user: prefixes in this codebase. Fixes openclaw#63329
Summary
Fixes the
thread_binding_invaliderror when spawning ACP sessions withthread: truefrom Discord guild channels after upgrading to 2026.4.5+.Fixes #63329
Root Cause
In
resolveChannelIdForBinding, theparams.threadIdis passed directly toRoutes.channel(). After the ACPX runtime refactor in #61319 (2026.4.5), the conversation ID is now passed in OpenClaw's internal prefixed format (channel:1490136404385075452) rather than as a raw numeric Discord ID.Routes.channel()(fromdiscord-api-types) URL-encodes the colon in thechannel:prefix, producing/channels/channel%3A1490136404385075452. Discord returns an error for this invalid path. The error is caught and swallowed silently, causingresolveChannelIdForBindingto returnnull,bindTargetto returnnull, and the spawn to fail withthread_binding_invalid.Fix
Strip the
channel:oruser:prefix fromparams.threadIdbefore passing it toRoutes.channel(), so the Discord REST API always receives a valid numeric channel ID.Testing
Verified locally by applying the equivalent patch to the built bundle (
/app/dist/thread-bindings.discord-api-CL8HMdV4.js). After the patch,sessions_spawnwiththread: truefrom a Discord guild text channel succeeds and creates a thread as expected.Notes
resolveDiscordChannelId()helper which already handles prefix stripping