Skip to content

fix(telegram): safe DM threadless retry for missing message_thread_id#30892

Merged
steipete merged 1 commit into
openclaw:mainfrom
liuxiaopai-ai:codex/issue-30674-telegram-thread-retry
Mar 2, 2026
Merged

fix(telegram): safe DM threadless retry for missing message_thread_id#30892
steipete merged 1 commit into
openclaw:mainfrom
liuxiaopai-ai:codex/issue-30674-telegram-thread-retry

Conversation

@liuxiaopai-ai

Copy link
Copy Markdown
Contributor

Summary

  • Problem: Telegram sends can fail with message thread not found and break delivery; naive threadless fallback can also misroute forum-topic replies and trigger false danger logs on successful retry.
  • Why it matters: delivery reliability should improve for DM-thread sessions without broadening delivery scope for forum topics.
  • What changed: added Telegram send helper that retries once without message_thread_id only for thread.scope === "dm", and suppresses first-attempt danger logging when that retry path is expected.
  • What did NOT change (scope boundary): no forum-topic threadless fallback, no routing-policy changes, no auth/network policy changes.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

  • DM-thread Telegram sends now recover from stale thread IDs by retrying once without message_thread_id.
  • Forum-thread sends remain fail-closed (no threadless retry), avoiding cross-topic misdelivery.
  • Successful DM fallback retries no longer emit danger-level error logs for the first failed attempt.

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)
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: Node 22 + pnpm
  • Model/provider: N/A
  • Integration/channel (if any): Telegram bot delivery unit tests
  • Relevant config (redacted): thread scope fixtures (dm / forum)

Steps

  1. Run pnpm exec vitest run src/telegram/bot/delivery.test.ts
  2. Run pnpm build
  3. Validate new tests for DM retry, forum fail-closed behavior, and media retry

Expected

  • DM thread-not-found retries once without message_thread_id and succeeds.
  • Forum thread-not-found does not retry threadless and throws.
  • No danger log for first failed attempt when DM retry succeeds.

Actual

  • Verified by new regression tests and passing build.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Commands run:

  • pnpm exec vitest run src/telegram/bot/delivery.test.ts
  • pnpm build
  • pnpm check (fails in this env due optional extension deps missing; unchanged baseline)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios:
    • DM text send retries without thread id and succeeds.
    • DM media send retries without thread id and succeeds.
    • Forum send does not retry threadless.
  • Edge cases checked:
    • Existing voice forbidden fallback behavior remains covered and passing.
    • Non-threaded sends unchanged.
  • What you did not verify:
    • Live Telegram bot end-to-end run in production chats.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: revert this PR commit.
  • Files/config to restore: src/telegram/bot/delivery.ts, src/telegram/bot/delivery.test.ts.
  • Known bad symptoms reviewers should watch for: DM retries not triggering for stale thread IDs, or unexpected forum threadless sends.

Risks and Mitigations

  • Risk: DM fallback could mask persistent thread-state issues by succeeding without thread id.
    • Mitigation: retry is single-shot and explicitly logged at runtime; forum scope remains fail-closed.

@aisle-research-bot

aisle-research-bot Bot commented Mar 1, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🔵 Low Telegram API error-level logging suppressed on substring match, reducing monitoring visibility

1. 🔵 Telegram API error-level logging suppressed on substring match, reducing monitoring visibility

Property Value
Severity Low
CWE CWE-778
Location src/telegram/bot/delivery.ts:563-569

Description

The new sendTelegramWithThreadFallback logic suppresses the error-level log for the first Telegram send failure when it intends to retry without message_thread_id.

This can weaken monitoring/alerting because:

  • withTelegramApiErrorLogging logs failures via runtime.error / subsystem .error (high-signal) and then rethrows.
  • For DM thread sends, the first failure is not logged at error level when the error message matches /message thread not found/i.
  • The suppression predicate is partially based on formatErrorMessage(err) for non-GrammyError values. formatErrorMessage will include err.message (or a provided string), which can be influenced by upstream/transport layers and (in some architectures) potentially by attacker-controlled content if any thrown error message incorporates user input. This increases the risk of accidentally suppressing logs for unrelated failures whose formatted message contains the trigger substring.

Vulnerable/suppression logic:

function isTelegramThreadNotFoundError(err: unknown): boolean {
  if (err instanceof GrammyError) {
    return THREAD_NOT_FOUND_RE.test(err.description);
  }
  return THREAD_NOT_FOUND_RE.test(formatErrorMessage(err));
}

const shouldSuppressFirstErrorLog = (err: unknown) =>
  allowThreadlessRetry && hasThreadId && isTelegramThreadNotFoundError(err);
const mergedShouldLog = params.shouldLog
  ? (err: unknown) => params.shouldLog!(err) && !shouldSuppressFirstErrorLog(err)
  : (err: unknown) => !shouldSuppressFirstErrorLog(err);

Impact:

  • Repeated triggering of “message thread not found” (legitimate or induced) will not show up in error-level logs if the retry succeeds, potentially hiding operational issues or abuse patterns from alerting tuned to error logs.
  • Because the match is substring-based and can fall back to formatErrorMessage, suppression is less strictly tied to Telegram’s actual API error semantics (e.g., error_code=400).

Recommendation

Tighten suppression to only apply to verified Telegram API thread-not-found errors, and still emit a high-signal event when suppression occurs.

Suggested changes:

  1. Only treat this as thread-not-found when the error is a Telegram API error with a known structure (prefer GrammyError fields):
function isTelegramThreadNotFoundError(err: unknown): boolean {
  return (
    err instanceof GrammyError &&
    err.error_code === 400 &&/\bmessage thread not found\b/i.test(err.description)
  );
}

(Optionally also handle HttpError from grammY by inspecting its structured payload, if available, rather than string matching.)

  1. When suppressing the first error log, emit a warning (or a structured metric/event) instead of runtime.log, so it is visible to monitoring without causing a full error alert flood:
params.runtime.error?.(
  `telegram ${params.operation}: thread not found; will retry without message_thread_id`
);

Or add a dedicated counter/telemetry hook.

  1. Consider narrowing the regex to match Telegram’s canonical message (as done elsewhere in the repo) to reduce accidental matches.

Analyzed PR: #30892 at commit 22b2eab

Last updated on: 2026-03-01T19:03:51Z

@openclaw-barnacle openclaw-barnacle Bot added channel: telegram Channel integration: telegram size: S labels Mar 1, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 22b2eab156

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +586 to +589
return await withTelegramApiErrorLogging({
operation: `${params.operation} (threadless retry)`,
runtime: params.runtime,
fn: () => params.send(retryParams),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve shouldLog filtering on threadless retry

Pass the caller's shouldLog predicate into the retry withTelegramApiErrorLogging call as well; otherwise DM-thread retries that hit handled errors on the second attempt (for example sendMessage parse/empty-text errors that fall back to plain text, or sendVoice VOICE_MESSAGES_FORBIDDEN that falls back to text) now emit danger logs even when delivery ultimately succeeds. This is a regression in log behavior introduced by the new retry path.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Mar 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Implements DM-specific thread fallback for Telegram sends that fail with message thread not found. The retry logic is correctly scoped to DM threads only (thread.scope === "dm"), preventing misrouting in forum topics. Error logging suppression for successful retries prevents false alarms.

  • New sendTelegramWithThreadFallback() wrapper handles retry logic consistently across all send operations (text, media, voice, etc.)
  • Retry only happens when all conditions are met: DM scope + thread ID present + thread-not-found error
  • Forum threads remain fail-closed (no threadless retry), maintaining delivery safety
  • Comprehensive test coverage for DM retry, forum fail-closed behavior, and media sends

Confidence Score: 4/5

  • This PR is safe to merge with minimal risk
  • Score reflects well-structured implementation with proper scope controls, comprehensive test coverage, and no security concerns. Point deducted for: (1) reliance on regex-based error message parsing which could break if Telegram changes error formats, and (2) lack of live production verification as noted by author
  • No files require special attention - all changes are straightforward and well-tested

Last reviewed commit: 22b2eab

@steipete steipete force-pushed the codex/issue-30674-telegram-thread-retry branch from 22b2eab to 3d77ab3 Compare March 2, 2026 02:58
@steipete steipete merged commit e6e3a7b into openclaw:main Mar 2, 2026
8 checks passed
@steipete

steipete commented Mar 2, 2026

Copy link
Copy Markdown
Contributor

Landed via temp rebase onto main.

  • Gate: pnpm vitest run src/telegram/bot/delivery.test.ts
  • Land commit: 3d77ab3
  • Merge commit: e6e3a7b

Thanks @liuxiaopai-ai!

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3d77ab3b4b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +630 to +633
return await withTelegramApiErrorLogging({
operation: `${params.operation} (threadless retry)`,
runtime: params.runtime,
fn: () => params.send(retryParams),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass shouldLog into threadless retry logger

The retry branch in sendTelegramWithThreadFallback calls withTelegramApiErrorLogging without forwarding params.shouldLog, so handled second-attempt errors now get danger-logged. Fresh evidence: sendTelegramText and sendVoice both pass filters (parse/empty text and VOICE_MESSAGES_FORBIDDEN) specifically so those fallback paths can succeed quietly, but after a DM message thread not found retry this omission causes noisy error logs even when delivery ultimately succeeds.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: telegram Channel integration: telegram size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Telegramm

2 participants