openclaw - 💡(How to fix) Fix Discord heartbeat DM targets degrade from user:<id> to channel:<id> [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#72977Fetched 2026-04-28 06:29:10
View on GitHub
Comments
1
Participants
2
Timeline
2
Reactions
0
Timeline (top)
closed ×1commented ×1

Discord heartbeat/session-derived DM routing can lose the user: prefix and degrade into channel:<id>, which causes outbound heartbeat delivery to fail with Unknown Channel.

Root Cause

Suspected root cause

Code Example

return {
  to: target.id,
  chatType: target.kind === "user" ? ("direct" as const) : ("channel" as const),
};

---

return {
  to: target.normalized,
  chatType: target.kind === "user" ? ("direct" as const) : ("channel" as const),
};

---

node scripts/run-vitest.mjs run extensions/discord/src/channel.test.ts
RAW_BUFFERClick to expand / collapse

Summary

Discord heartbeat/session-derived DM routing can lose the user: prefix and degrade into channel:<id>, which causes outbound heartbeat delivery to fail with Unknown Channel.

Environment

  • OpenClaw version: 2026.4.25-beta.8
  • Repo HEAD observed: ec71b01f71ad5156dd125fd019a9c59e29a3d53d
  • Host OS: Windows
  • Channel: Discord

Repro

  1. Configure Discord DMs so the session's external route is a DM target like user:<discord-user-id>.
  2. Use heartbeat delivery that resolves through session/external-target routing (for example target: "last", optionally with a direct-session heartbeat config).
  3. Trigger a heartbeat run or let delivery recovery retry pending heartbeat deliveries.
  4. Inspect the resulting outbound delivery target / queue entry / session metadata.

Expected

A DM target remains user:<id> all the way through outbound resolution, and the heartbeat can be delivered to the Discord DM.

Actual

The target can be degraded to a bare numeric ID and then normalized as channel:<id>, which causes heartbeat delivery to fail with Unknown Channel.

Observed symptoms included:

  • direct sends to user:<id> work
  • heartbeat-related deliveries were enqueued as channel:<id>
  • retry queue entries reported lastError: "Unknown Channel"

Suspected root cause

In extensions/discord/src/channel.ts, parseDiscordExplicitTarget() returns target.id instead of target.normalized:

return {
  to: target.id,
  chatType: target.kind === "user" ? ("direct" as const) : ("channel" as const),
};

That drops the explicit target kind for DMs. Later outbound normalization treats the bare numeric ID as a channel target, which turns user:<id> into channel:<id>.

Local validation

Changing that one field to target.normalized fixed the built plugin behavior locally:

return {
  to: target.normalized,
  chatType: target.kind === "user" ? ("direct" as const) : ("channel" as const),
};

I also added and ran a regression test in extensions/discord/src/channel.test.ts to verify that:

  • parsing user:123456789012345678 returns { to: "user:123456789012345678", chatType: "direct" }

The narrow source test passed locally after the fix:

node scripts/run-vitest.mjs run extensions/discord/src/channel.test.ts

Suggested fix

Preserve the canonical normalized target in parseDiscordExplicitTarget() by returning target.normalized instead of target.id.

extent analysis

TL;DR

The most likely fix is to update the parseDiscordExplicitTarget() function to return target.normalized instead of target.id to preserve the canonical normalized target.

Guidance

  • Verify the issue by checking if the parseDiscordExplicitTarget() function is indeed returning target.id instead of target.normalized, which causes the target to be degraded to a bare numeric ID.
  • Update the parseDiscordExplicitTarget() function to return target.normalized to fix the issue, as shown in the local validation section of the issue.
  • Run the regression test in extensions/discord/src/channel.test.ts to verify that the fix works as expected.
  • Consider adding more tests to cover different scenarios and ensure the fix does not introduce any new issues.

Example

return {
  to: target.normalized,
  chatType: target.kind === "user" ? ("direct" as const) : ("channel" as const),
};

Notes

The suggested fix is based on the local validation and regression test provided in the issue, which suggests that updating the parseDiscordExplicitTarget() function to return target.normalized fixes the issue.

Recommendation

Apply the workaround by updating the parseDiscordExplicitTarget() function to return target.normalized instead of target.id, as this fix has been locally validated and verified through regression testing.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix Discord heartbeat DM targets degrade from user:<id> to channel:<id> [1 comments, 2 participants]