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
- Open a DM topic with the bot (e.g. topic ID
1045687)
- Trigger a
sessions_spawn subagent from that topic
- Wait for subagent to complete
- 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
- Logging: Include
message_thread_id (if present) in the sendMessage ok log line
- 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
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
1045687)sessions_spawnsubagent from that topic1045687Expected Behavior
The announce should be delivered to the same topic where the spawn was triggered, using the
threadIdcaptured inrequesterOriginat 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'slastThreadId.Analysis
The
requesterOrigincorrectly capturesthreadIdat spawn time inspawnSubagentDirect()(line ~333 insubagent-spawn.ts):However, at announce time in
runSubagentAnnounceFlow(), the main session entry (agent:main:main) may have a stalelastThreadId/deliveryContext.threadIdfrom a different topic interaction that happened between spawn and completion.When
expectsCompletionMessage=true(the default), the flow goes throughresolveSubagentCompletionOrigin()->createBoundDeliveryRouter().resolveDestination(). For non-thread-bound spawns, this falls back correctly torequesterOrigin. But the final delivery path throughsendSubagentAnnounceDirectly()may lose the threadId.Additionally, there is a guard in
resolveAnnounceOrigin()(added in commit8178ea472dfor Discord threading) that actively stripsthreadIdfrom the fallback entry when the requester hastobut nothreadId:Observability Gap
The gateway log only shows
[telegram] sendMessage ok chat=63448508 message=XXXXXwithout themessage_thread_idparameter, making it impossible to diagnose from logs alone whether the threadId was passed correctly.Suggested Fix
message_thread_id(if present) in thesendMessage oklog linethreadIdfromrequesterOriginsurvives the full announce pipeline for Telegram DM topics (verify with a test case)Environment
agent:main:main) with DM topics