openclaw - ✅(Solved) Fix [Bug]: Subagent announce delivers to wrong Telegram DM topic (stale threadId) [2 pull requests, 2 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#52217Fetched 2026-04-08 01:14:09
View on GitHub
Comments
2
Participants
2
Timeline
19
Reactions
0
Author
Timeline (top)
referenced ×12cross-referenced ×3commented ×2closed ×1

Root Cause

The requesterOrigin correctly captures threadId at spawn time in spawnSubagentDirect() (line ~333 in subagent-spawn.ts):

const requesterOrigin = normalizeDeliveryContext({
  channel: ctx.agentChannel,
  accountId: ctx.agentAccountId,
  to: ctx.agentTo,
  threadId: ctx.agentThreadId,
});

However, at announce time in runSubagentAnnounceFlow(), the main session entry (agent:main:main) may have a stale lastThreadId / deliveryContext.threadId from a different topic interaction that happened between spawn and completion.

When expectsCompletionMessage=true (the default), the flow goes through resolveSubagentCompletionOrigin() -> createBoundDeliveryRouter().resolveDestination(). For non-thread-bound spawns, this falls back correctly to requesterOrigin. But the final delivery path through sendSubagentAnnounceDirectly() may lose the threadId.

Additionally, there is a guard in resolveAnnounceOrigin() (added in commit 8178ea472d for Discord threading) that actively strips threadId from the fallback entry when the requester has to but no threadId:

const entryForMerge =
    normalizedRequester?.to &&
    normalizedRequester.threadId == null &&
    normalizedEntry?.threadId != null
      ? (() => {
          const { threadId: _ignore, ...rest } = normalizedEntry;
          return rest;
        })()
      : normalizedEntry;

Fix Action

Fixed

PR fix notes

PR #52824: fix(announce): preserve threadId in subagent announce for Telegram DM topics

Description (problem / solution / changelog)

Summary

Fixes #52217 — subagent completion announce was delivering to the main Telegram DM chat instead of the originating topic because threadId was lost during the announce delivery pipeline.

  • Swap conversationId priority in resolveSubagentCompletionOrigin: prefer to-derived chat ID over threadId for bound-route lookups, so bindings match the conversation (chat) rather than the topic thread inside it
  • Defensive threadId guard in resolveAnnounceOrigin: ensure the requester's threadId (captured at spawn time) is never overridden by a stale session entry lastThreadId
  • Telegram observability: add message_thread_id to sendMessage ok log lines for easier diagnosis
  • 3 regression tests: direct path (no entry threadId), queue path (stale entry threadId), and no-spurious-injection scenario

Test plan

  • pnpm test -- src/agents/subagent-announce.format.e2e.test.ts — 73 tests pass (including 3 new)
  • pnpm test -- src/utils/delivery-context.test.ts — 9 tests pass
  • pnpm check — all lint/format checks pass
  • Manual: spawn subagent from Telegram DM topic, verify completion announce lands in the same topic

Changed files

  • extensions/telegram/src/bot/delivery.send.ts (modified, +8/-2)
  • src/agents/subagent-announce.format.e2e.test.ts (modified, +119/-0)
  • src/agents/subagent-announce.ts (modified, +15/-2)

PR #52900: fix: populate currentThreadTs in threading tool context fallback (#52217)

Description (problem / solution / changelog)

Problem

When a channel plugin lacks a custom buildToolContext in its threading adapter (e.g. Telegram), the fallback path in buildThreadingToolContext() did not include currentThreadTs from the inbound MessageThreadId.

This caused resolveTelegramAutoThreadId() to return undefined (because it requires both currentThreadTs AND currentChannelId), so any message(action=send) without an explicit threadId would route to the main chat instead of the originating DM topic.

Reproduction

  1. Open a DM topic with the bot (e.g. topic 1045687)
  2. Send a message in another topic or main chat (updates lastThreadId in session entry)
  3. Return to original topic - agent sends message(action=send) with buttons/media without explicit threadId
  4. Result: Message arrives in main chat instead of topic

Fix

Add currentThreadTs derived from sessionCtx.MessageThreadId to the fallback return in buildThreadingToolContext() when threading?.buildToolContext is absent.

This ensures resolveTelegramAutoThreadId() can auto-inject the correct message_thread_id for Telegram DM topics, matching the behavior that channels with custom buildToolContext already have.

Changes

  • src/auto-reply/reply/agent-runner-utils.ts: Add currentThreadTs to fallback path (3 lines)

Testing

  • Existing threading tests pass (message-action-runner.threading.test.ts, channel.test.ts)
  • Manually verified: message(action=send) without threadId now correctly routes to topic after the user interacts with main chat

Fixes #52217

Changed files

  • src/auto-reply/reply/agent-runner-utils.ts (modified, +3/-0)

Code Example

const requesterOrigin = normalizeDeliveryContext({
  channel: ctx.agentChannel,
  accountId: ctx.agentAccountId,
  to: ctx.agentTo,
  threadId: ctx.agentThreadId,
});

---

const entryForMerge =
    normalizedRequester?.to &&
    normalizedRequester.threadId == null &&
    normalizedEntry?.threadId != null
      ? (() => {
          const { threadId: _ignore, ...rest } = normalizedEntry;
          return rest;
        })()
      : normalizedEntry;
RAW_BUFFERClick to expand / collapse

Bug Description

When a subagent is spawned from a Telegram DM topic conversation (using sessions_spawn), the completion announce is delivered to the main chat / wrong topic instead of the originating topic.

Steps to Reproduce

  1. Open a DM topic with the bot (e.g. topic ID 1045687)
  2. Trigger a sessions_spawn subagent from that topic
  3. Wait for subagent to complete
  4. The announce result appears in the main chat (General topic or a different topic), not in topic 1045687

Expected Behavior

The announce should be delivered to the same topic where the spawn was triggered, using the threadId captured in requesterOrigin at spawn time.

Actual Behavior

The announce is delivered to the main chat without message_thread_id, or to a stale topic from the session entry's lastThreadId.

Analysis

The requesterOrigin correctly captures threadId at spawn time in spawnSubagentDirect() (line ~333 in subagent-spawn.ts):

const requesterOrigin = normalizeDeliveryContext({
  channel: ctx.agentChannel,
  accountId: ctx.agentAccountId,
  to: ctx.agentTo,
  threadId: ctx.agentThreadId,
});

However, at announce time in runSubagentAnnounceFlow(), the main session entry (agent:main:main) may have a stale lastThreadId / deliveryContext.threadId from a different topic interaction that happened between spawn and completion.

When expectsCompletionMessage=true (the default), the flow goes through resolveSubagentCompletionOrigin() -> createBoundDeliveryRouter().resolveDestination(). For non-thread-bound spawns, this falls back correctly to requesterOrigin. But the final delivery path through sendSubagentAnnounceDirectly() may lose the threadId.

Additionally, there is a guard in resolveAnnounceOrigin() (added in commit 8178ea472d for Discord threading) that actively strips threadId from the fallback entry when the requester has to but no threadId:

const entryForMerge =
    normalizedRequester?.to &&
    normalizedRequester.threadId == null &&
    normalizedEntry?.threadId != null
      ? (() => {
          const { threadId: _ignore, ...rest } = normalizedEntry;
          return rest;
        })()
      : normalizedEntry;

Observability Gap

The gateway log only shows [telegram] sendMessage ok chat=63448508 message=XXXXX without the message_thread_id parameter, making it impossible to diagnose from logs alone whether the threadId was passed correctly.

Suggested Fix

  1. Logging: Include message_thread_id (if present) in the sendMessage ok log line
  2. Root cause: Ensure the threadId from requesterOrigin survives the full announce pipeline for Telegram DM topics (verify with a test case)

Environment

  • OpenClaw version: latest (March 2026)
  • Channel: Telegram (DM with bot topics enabled)
  • Session type: main session (agent:main:main) with DM topics

extent analysis

Fix Plan

To fix the issue, we need to ensure that the threadId from requesterOrigin is preserved throughout the announce pipeline for Telegram DM topics. Here are the steps:

  • Update logging: Modify the sendMessage ok log line to include message_thread_id (if present) for better observability.
  • Preserve threadId: Update resolveAnnounceOrigin() to preserve the threadId from requesterOrigin when expectsCompletionMessage=true. Remove the guard that strips threadId from the fallback entry.

Example code changes:

// Update logging in sendMessage function
logger.info(`[telegram] sendMessage ok chat=${chatId} message=${message} threadId=${threadId}`);

// Update resolveAnnounceOrigin function
const entryForMerge =
  normalizedRequester?.to && normalizedRequester.threadId != null
    ? normalizedRequester
    : normalizedEntry;
  • Verify threadId preservation: Add a test case to ensure that the threadId from requesterOrigin is correctly delivered to the announce pipeline for Telegram DM topics.

Verification

To verify that the fix worked:

  • Trigger a sessions_spawn subagent from a Telegram DM topic.
  • Wait for the subagent to complete.
  • Check that the announce result appears in the same topic where the spawn was triggered.
  • Verify that the message_thread_id is correctly logged in the sendMessage ok log line.

Extra Tips

  • Ensure that the threadId is correctly captured in requesterOrigin at spawn time.
  • Review the announce pipeline to ensure that the threadId is not lost or overwritten.
  • Consider adding additional logging or debugging statements to help diagnose any future issues with threadId preservation.

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 [Bug]: Subagent announce delivers to wrong Telegram DM topic (stale threadId) [2 pull requests, 2 comments, 2 participants]