openclaw - ✅(Solved) Fix Discord: resolveDiscordThreadStarter does not extract Forwarded Message content from message_snapshots [1 pull requests, 1 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#60129Fetched 2026-04-08 02:35:56
View on GitHub
Comments
0
Participants
1
Timeline
8
Reactions
0
Author
Participants
Timeline (top)
referenced ×5closed ×1cross-referenced ×1locked ×1

Root Cause

In resolveDiscordThreadStarter (threading source, ~L616):

const content = starter.content?.trim() ?? "";
const embedText = resolveDiscordEmbedText(starter.embeds?.[0]);
const text = content || embedText;
if (!text) return null; // ← FM has empty content, falls through

FM messages have:

  • content: "" (empty)
  • flags: 16384 (IS_FORWARD)
  • message_snapshots[0].message.content ← actual text is here

Meanwhile, resolveDiscordMessageText (L360) correctly handles this via resolveDiscordForwardedMessagesText().

Fix Action

Fix / Workaround

The workflow:

  1. Channel = Project (e.g. #job-hunt, #openclaw-dev)
  2. Message = Task — posted by human or agent
  3. Thread = Task workspace — progress updates, agent collaboration
  4. Forward = Subtask dispatch — a thread message forwarded to another channel becomes a new task with traceability back to the parent

PR fix notes

PR #60139: fix(discord): extract forwarded message text in thread starter resolution

Description (problem / solution / changelog)

Summary

  • Problem: resolveDiscordThreadStarter returns null for Discord Forwarded Messages because it only checks content and embeds, but FMs store their text in message_snapshots[0].message.content while content is empty ("")
  • Why it matters: includeThreadStarter: true is broken for FM-based threads — the agent session receives zero context about what was forwarded. This blocks FM-based task dispatch workflows (Channel→Forward→Thread→Agent)
  • What changed: Added a local resolveStarterForwardedText helper in threading.ts that extracts text from message_snapshots on the REST response object, used as a third fallback after content and embeds
  • What did NOT change: No changes to message-utils.ts. No changes to how forwarded messages are handled in regular message processing (resolveDiscordMessageText). No export surface changes.

Change Type (select all)

  • Bug fix

Scope (select all touched areas)

  • Integrations

Linked Issue/PR

  • Closes #60129
  • This PR fixes a bug or regression

Root Cause / Regression History (if applicable)

  • Root cause: resolveDiscordThreadStarter was written before Discord added the Forward feature (message_reference type 1). It only handles content and embeds, which covers normal messages and rich embeds but not forwarded messages where content is empty and the actual text is in message_snapshots.
  • Missing detection / guardrail: No test case for forwarded message as thread starter.
  • Prior context: The regular message path (resolveDiscordMessageText at L525) already handles message_snapshots correctly via resolveDiscordForwardedMessagesText. The thread starter path was simply never updated.
  • Why this regressed now: Not a regression — it was never supported. Discord's Forward feature is relatively new.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
  • Target test or file: threading.starter.test.ts
  • Scenario the test should lock in: Thread starter with empty content + populated message_snapshots → text extracted
  • Why this is the smallest reliable guardrail: Unit test on resolveDiscordThreadStarter with mocked REST response covers the exact code path
  • 3 new tests added:
    1. FM with content in message_snapshots → extracts text
    2. Normal message with content + snapshots → prefers content
    3. Empty content + empty snapshots → returns null

User-visible / Behavior Changes

When includeThreadStarter: true is configured and a thread is created on a Forwarded Message, the agent session now receives the forwarded text as context. Previously it received nothing.

Diagram (if applicable)

Before:
[FM as thread starter] -> resolveDiscordThreadStarter
  -> content="" -> embeds=[] -> return null ❌

After:
[FM as thread starter] -> resolveDiscordThreadStarter
  -> content="" -> embeds=[] -> message_snapshots[0].message.content="task text"
  -> return "[Forwarded message]\ntask text" ✅

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: macOS (Darwin 25.4.0 arm64)
  • Runtime: Node v22.17.0
  • OpenClaw: latest npm (2026.4.3)
  • Integration: Discord with includeThreadStarter: true

Steps

  1. Forward a message to a Discord channel
  2. Create a thread on the forwarded message (manual or autoThread)
  3. Mention the bot in the thread

Expected

Session includes forwarded message text as thread starter context

Actual (before fix)

Thread starter is null, session has no FM context

Evidence

  • Failing test/log before + passing after
  • All 6 tests pass (3 existing + 3 new): vitest run threading.starter.test.ts
  • Pre-commit hooks pass: pnpm check (tsgo, lint, all checks)
  • Verified with live Discord API data: FM message has content: "", flags: 16416 (IS_FORWARD + HAS_THREAD), text in message_snapshots[0].message.content

Human Verification (required)

  • Verified scenarios: Inspected live Discord API responses for forwarded messages, confirmed content is empty and message_snapshots contains the text. Ran full test suite locally.
  • Edge cases checked: Empty snapshots array, snapshots with no message, normal messages with both content and snapshots (content takes priority)
  • What I did NOT verify: End-to-end with a running OpenClaw instance (verified data structure against live API responses and unit tests only)

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

v1 had Greptile concerns about fragile as unknown as Message cast and unnecessary export of resolveDiscordMessageSnapshots. v2 addresses both: uses a self-contained local helper (resolveStarterForwardedText) that operates directly on the typed REST response, no cross-module imports or casts needed.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: message_snapshots structure could change in future Discord API versions
    • Mitigation: The helper gracefully returns "" for any unexpected structure (missing array, missing message, missing content). Falls through to existing return null path.

AI-Assisted

  • AI-assisted (OpenClaw agent authored the code with human direction and review)
  • Fully tested (6 unit tests, pre-commit checks)
  • Understand what the code does

Changed files

  • extensions/discord/src/monitor/threading.starter.test.ts (modified, +93/-0)
  • extensions/discord/src/monitor/threading.ts (modified, +38/-1)

Code Example

const content = starter.content?.trim() ?? "";
const embedText = resolveDiscordEmbedText(starter.embeds?.[0]);
const text = content || embedText;
if (!text) return null; // ← FM has empty content, falls through

---

const content = starter.content?.trim() ?? "";
const embedText = resolveDiscordEmbedText(starter.embeds?.[0]);
const forwardedText = resolveDiscordForwardedMessagesText(starter);
const text = content || embedText || forwardedText;

---

{
  "content": "",
  "flags": 16416,
  "message_reference": { "type": 1, "channel_id": "...", "message_id": "..." },
  "message_snapshots": [{
    "message": {
      "content": "actual forwarded text here",
      "timestamp": "..."
    }
  }]
}
RAW_BUFFERClick to expand / collapse

Why This Matters

Discord's Forward feature enables a powerful workflow pattern: using a Discord server as a Slack-like workspace where channels represent projects and messages represent tasks.

The workflow:

  1. Channel = Project (e.g. #job-hunt, #openclaw-dev)
  2. Message = Task — posted by human or agent
  3. Thread = Task workspace — progress updates, agent collaboration
  4. Forward = Subtask dispatch — a thread message forwarded to another channel becomes a new task with traceability back to the parent

This turns Discord into an agent-native task management system. The agent can process forwarded messages as subtasks, trace back to the original context via message_reference, and collaborate in threads.

But today this is broken: when a Forwarded Message is the thread starter, the agent's session receives zero context about what was forwarded. The agent has to blindly call message read on the thread starter to discover the FM content — and only if it's been instructed to do so via systemPrompt. This defeats the purpose of includeThreadStarter.

Bug Description

When a Discord Forwarded Message (FM) is used as a thread starter, resolveDiscordThreadStarter returns null because it only checks content and embeds, but FM messages store their text in message_snapshots[0].message.content while content is empty ("").

This means includeThreadStarter: true has no effect for FM-based threads — the session receives no context about the forwarded message.

Steps to Reproduce

  1. Configure a Discord channel with includeThreadStarter: true
  2. Forward a message to that channel (Discord Forward / message_reference type 1)
  3. Create a thread on the forwarded message (manual or autoThread)
  4. Mention the bot in the thread

Expected: Session includes the forwarded message text as thread starter context Actual: Thread starter is null, session has no FM context

Root Cause

In resolveDiscordThreadStarter (threading source, ~L616):

const content = starter.content?.trim() ?? "";
const embedText = resolveDiscordEmbedText(starter.embeds?.[0]);
const text = content || embedText;
if (!text) return null; // ← FM has empty content, falls through

FM messages have:

  • content: "" (empty)
  • flags: 16384 (IS_FORWARD)
  • message_snapshots[0].message.content ← actual text is here

Meanwhile, resolveDiscordMessageText (L360) correctly handles this via resolveDiscordForwardedMessagesText().

Proposed Fix

Add message_snapshots fallback in resolveDiscordThreadStarter, reusing existing helpers:

const content = starter.content?.trim() ?? "";
const embedText = resolveDiscordEmbedText(starter.embeds?.[0]);
const forwardedText = resolveDiscordForwardedMessagesText(starter);
const text = content || embedText || forwardedText;

Verified Data

FM message structure from Discord API:

{
  "content": "",
  "flags": 16416,
  "message_reference": { "type": 1, "channel_id": "...", "message_id": "..." },
  "message_snapshots": [{
    "message": {
      "content": "actual forwarded text here",
      "timestamp": "..."
    }
  }]
}

Environment

  • OpenClaw latest (npm)
  • Discord channel with includeThreadStarter: true
  • Thread created on a Forwarded Message

extent analysis

TL;DR

Update the resolveDiscordThreadStarter function to handle Discord Forwarded Messages by adding a fallback to check message_snapshots for the actual text content.

Guidance

  • Identify the resolveDiscordThreadStarter function and update it to include a check for message_snapshots when the content field is empty.
  • Reuse the existing resolveDiscordForwardedMessagesText helper function to extract the text from message_snapshots.
  • Verify that the updated function correctly handles Forwarded Messages by checking the flags field for the IS_FORWARD flag (16384).
  • Test the updated function with a sample Forwarded Message to ensure it returns the correct text content.

Example

const content = starter.content?.trim() ?? "";
const embedText = resolveDiscordEmbedText(starter.embeds?.[0]);
const forwardedText = resolveDiscordForwardedMessagesText(starter);
const text = content || embedText || forwardedText;

Notes

This fix assumes that the resolveDiscordForwardedMessagesText function is correctly implemented and can handle the message_snapshots field. Additionally, this fix only addresses the issue with Forwarded Messages and may not affect other types of messages.

Recommendation

Apply the proposed fix to the resolveDiscordThreadStarter function to correctly handle Discord Forwarded Messages and provide the expected thread starter context.

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 - ✅(Solved) Fix Discord: resolveDiscordThreadStarter does not extract Forwarded Message content from message_snapshots [1 pull requests, 1 participants]