-
-
Notifications
You must be signed in to change notification settings - Fork 39.6k
Description
fix(telegram): restore thread_id=1 handling for DMs (regression from 19b8416)
Problem Description
Sub-agent announce messages fail in Telegram DMs with error: "Bad Request: message thread not found"
When a sub-agent completes its task and OpenClaw attempts to announce the result back to the main session via Telegram, the delivery fails with Telegram API error 400. This only affects private chats (DMs), not group chats or forum topics.
Root Cause Analysis
1. PR #848 (Jan 16) - Correct Fix
PR #848 (fix(telegram): skip message_thread_id for General topic messages) correctly handled the Telegram General topic (thread_id=1) inconsistency:
- Problem: Telegram rejects
sendMessagewithmessage_thread_id=1for General forum topics - Fix: In
buildTelegramThreadParams(), skip thread_id=1 entirely:if (normalized === TELEGRAM_GENERAL_TOPIC_ID) { return undefined; }
2. Commit 19b8416 (Feb 2) - Regression
Commit 19b8416a8 ("fix: unify telegram thread handling") introduced a regression by:
- Adding
scopeconcept ("dm" | "forum" | "none") - Modifying the logic to only skip thread_id=1 when
scope === "forum":if (normalized === TELEGRAM_GENERAL_TOPIC_ID && thread.scope === "forum") { return undefined; }
3. The Bug
In private chats (DMs):
threadSpec.scope = "dm"threadSpec.id = 1(default or from configuration)- New logic:
1 === 1 && "dm" === "forum"→ false (does NOT skip) - Result: Sends
message_thread_id: 1to Telegram API - Telegram response:
"Bad Request: message thread not found"
Code Evidence
Before (PR #848 correct):
// src/telegram/bot/helpers.ts (after PR #848)
export function buildTelegramThreadParams(messageThreadId?: number) {
if (messageThreadId == null) {
return undefined;
}
const normalized = Math.trunc(messageThreadId);
if (normalized === TELEGRAM_GENERAL_TOPIC_ID) {
return undefined; // ← SKIPS thread_id=1 for ALL cases
}
return { message_thread_id: normalized };
}After (Commit 19b8416 broken):
// src/telegram/bot/helpers.ts (current)
export function buildTelegramThreadParams(thread?: TelegramThreadSpec | null) {
if (!thread?.id) {
return undefined;
}
const normalized = Math.trunc(thread.id);
if (normalized === TELEGRAM_GENERAL_TOPIC_ID && thread.scope === "forum") {
return undefined; // ← Only skips when scope="forum"
}
return { message_thread_id: normalized };
}Impact
- Sub-agent announcements fail in Telegram DMs
- User sees: Main session receives the result, but Telegram delivery fails
- Error logs: Frequent
"Bad Request: message thread not found"errors - Affected flow: Sub-agent → OpenClaw announce → Telegram delivery (step 2 fails)
Error Logs (Redacted)
Actual error logs from production (user IDs and tokens redacted):
2026-02-06T22:51:39.868Z [telegram] message failed: Call to 'sendMessage' failed! (400: Bad Request: message thread not found)
2026-02-06T22:51:39.871Z [tools] message failed: Call to 'sendMessage' failed! (400: Bad Request: message thread not found)
2026-02-07T01:32:51.469Z [telegram] message failed: Call to 'sendMessage' failed! (400: Bad Request: message thread not found)
2026-02-07T01:32:51.471Z Delivery failed (telegram to telegram:[REDACTED_USER_ID]): GrammyError: Call to 'sendMessage' failed! (400: Bad Request: message thread not found)
Pattern: The error occurs consistently when OpenClaw attempts to deliver sub-agent announce messages to Telegram DMs.
Environment
- OpenClaw version: 2026.2.4+
- Telegram Bot API: Latest
- Chat type: Private chat (DM)
- Feature affected: Sub-agent announce delivery
Proposed Fix
Option A (Restore PR #848 logic):
export function buildTelegramThreadParams(thread?: TelegramThreadSpec | null) {
if (!thread?.id) {
return undefined;
}
const normalized = Math.trunc(thread.id);
// Restore: skip thread_id=1 for ALL scopes
if (normalized === TELEGRAM_GENERAL_TOPIC_ID) {
return undefined;
}
return { message_thread_id: normalized };
}Option B (More precise):
export function buildTelegramThreadParams(thread?: TelegramThreadSpec | null) {
if (!thread?.id) {
return undefined;
}
const normalized = Math.trunc(thread.id);
// Skip thread_id=1 OR skip for DM scope (DMs shouldn't have threads)
if (normalized === TELEGRAM_GENERAL_TOPIC_ID || thread.scope === "dm") {
return undefined;
}
return { message_thread_id: normalized };
}Option C (Recommended - clearest semantics):
export function buildTelegramThreadParams(thread?: TelegramThreadSpec | null) {
if (!thread?.id) {
return undefined;
}
const normalized = Math.trunc(thread.id);
// Never send thread_id for DMs (threads don't exist in private chats)
if (thread.scope === "dm") {
return undefined;
}
// Telegram rejects message_thread_id=1 for General forum topic
if (normalized === TELEGRAM_GENERAL_TOPIC_ID) {
return undefined;
}
return { message_thread_id: normalized };
}Testing Checklist
1. DM (Private Chat)
- Sub-agent announcement succeeds
- No "message thread not found" errors in logs
- Works with
defaultThreadId = 1in config (if applicable)
2. Forum Topics
- General topic (id=1) messages work (thread_id omitted)
- Specific topics (id>1) work (thread_id included)
- Typing indicators appear in General topic (requires thread_id)
3. Regular Groups
- Messages deliver without thread_id
- Reply threads don't create separate sessions
4. Typing Indicators
-
buildTypingThreadParams()still works correctly (separate function) - General topic typing indicators appear (requires thread_id=1)
Note: buildTypingThreadParams() uses raw messageThreadId and is NOT affected by this regression. It correctly includes thread_id=1 for typing indicators in General forum topics.
Related Issues/PRs
- PR fix(telegram): skip message_thread_id for General topic messages #848: Original fix for General topic thread_id=1
- Issue feat(telegram): fallback to main chat when topic delivery fails #2122: Fallback when topic delivery fails (closed)
- Commit 19b8416: Introduced the regression
Additional Context
Why DMs have thread_id=1
The messageThreadId=1 in DMs typically comes from:
- User's
openclaw.jsonconfig:agent.defaultThreadId = 1 - OR session state from previous forum interactions
- OR default value in some configuration
Telegram API Design
- Private chats (DMs): Do NOT support threads/topics
- Forum groups: Require
thread_idfor topics (except General topic id=1) - Regular groups: Ignore
thread_id(reply threads ≠ topics)
Note on Typing Indicators
buildTypingThreadParams() is a separate function that:
- Uses raw
messageThreadId, notTelegramThreadSpec - General topic (id=1) NEEDS
thread_idfor typing indicators to appear - This function is NOT affected by the regression
The resolveTelegramThreadSpec function returns scope: "dm" for private chats, which is correct. However, DMs should not have thread_id at all (or it should be ignored). The bug occurs because some configuration or session state sets messageThreadId=1 for DMs, and the new logic incorrectly tries to send it.
This is a clear regression that needs to be fixed to restore proper sub-agent announce functionality in Telegram DMs.