Skip to content

fix(telegram): use idempotent retry context for delete/reaction#96612

Merged
vincentkoc merged 1 commit into
openclaw:mainfrom
miorbnli:fix/telegram-idempotent-retry-context
Jun 27, 2026
Merged

fix(telegram): use idempotent retry context for delete/reaction#96612
vincentkoc merged 1 commit into
openclaw:mainfrom
miorbnli:fix/telegram-idempotent-retry-context

Conversation

@miorbnli

Copy link
Copy Markdown
Contributor

What Problem This Solves

reactMessageTelegram and deleteMessageTelegram (extensions/telegram/src/send.ts) passed context: "send" to isRecoverableTelegramNetworkError. The "send" context is the only one that defaults allowMessageMatch to false, disabling message-snippet matching. Both operations are idempotent (api.setMessageReaction / api.deleteMessage are safe to repeat), so a transient error recognizable only by message snippet (e.g. socket hang up, undici network error with no error code) was NOT retried — stricter than every sibling context (polling, webhook, unknown all default allowMessageMatch to true). Users saw spurious reaction/delete failures on transient network blips.

Why This Change Was Made

isRecoverableTelegramNetworkError's context encodes whether the caller is a non-idempotent write; only "send" opts into the strict path (options.context !== "send" at network-errors.ts:309). Grouping idempotent delete/reaction under "send" is a classification error. The existing test "skips broad message matches for send context" already locks the send-vs-polling contract, confirming the semantics are intentional — delete/react were simply mislabeled.

User Impact

Transient network errors during emoji reactions (approval bubbles, user-feedback reactions) and message cleanup deletes now retry like polling instead of failing immediately. sendMessage behavior is unchanged (still strict, no duplicate-message risk).

Real behavior proof

  • Behavior addressed: reactMessageTelegram / deleteMessageTelegram used context: "send", so transient snippet-only network errors were not retried even though both operations are idempotent.

  • Real environment tested: Linux x86_64, Node 22, commit a85f1d59c3 on branch fix/telegram-idempotent-retry-context.

  • Exact steps or command run after this patch: node --import tsx /tmp/verify-telegram-retry-context.mjs — imports the real isRecoverableTelegramNetworkError and checks snippet-only transient errors across retry contexts.

  • Evidence after fix: Terminal capture of node runtime verification (copied live output):

    Terminal capture of node runtime verification (live stdout):
      scenario: transient snippet-only network errors (no error code)
      fix: idempotent react/delete must retry like polling, not send
    
      err="socket hang up"
        send    (non-idempotent) : false
        react   (idempotent FIX) : true
        delete  (idempotent FIX) : true
        polling (sibling)        : true
        => react/delete retry like polling AND send stays strict: PASS
    
      err="Undici: network error"
        send    (non-idempotent) : false
        react   (idempotent FIX) : true
        delete  (idempotent FIX) : true
        polling (sibling)        : true
        => react/delete retry like polling AND send stays strict: PASS
    
    Verdict: PASS
    
  • Observed result after fix: For snippet-only transient errors, react/delete now return recoverable (retry), matching polling; send stays non-recoverable.

  • What was not tested: Live end-to-end against a real Telegram bot under an injected transient socket error (function-level proof covers the retry predicate directly); sendChatAction also uses context: "send" but is out of scope here.

Tests and validation

$ pnpm test extensions/telegram/src/network-errors.test.ts
Test Files  1 passed (1)
Tests       52 passed (52)

$ pnpm tsgo:extensions:test
EXIT=0 (type check passed)

Added one regression case to network-errors.test.ts: delete/react contexts treat a snippet-only error as recoverable (like polling), while send stays strict.

Risk checklist

  • User-visible behavior: Yes - transient network errors during reaction/delete now retry instead of surfacing as immediate failures; intended for idempotent ops, adds no duplicate-message risk.
  • Config/env/migration behavior: No - no config or env surface touched.
  • Security/auth/secrets/network/tool execution: No - only the retry predicate's context label changes; no new command execution, credential, or network path.
  • Plugins/providers/channels/SDK: No - internal to the telegram plugin; TelegramNetworkErrorContext gains two additive enum values, helper default logic unchanged.
  • Highest-risk area: slightly more retry traffic on reaction/delete under flaky networks, but both are idempotent Telegram API calls with no correctness impact.
  • Mitigation: sendMessage keeps "send" (strict); existing send-vs-polling contract test plus the new delete/react regression case guard the semantics.

Closes: N/A (no existing issue)

reactMessageTelegram and deleteMessageTelegram passed context: "send" to
isRecoverableTelegramNetworkError, which disables message-snippet matching
(allowMessageMatch defaults to false only for "send"). Both operations are
idempotent (setMessageReaction / deleteMessage are safe to repeat), yet a
transient snippet-only network error (e.g. "socket hang up", "undici network
error" with no error code) was not retried — stricter than polling/webhook/
unknown, which all default allowMessageMatch to true. Users saw spurious
reaction/delete failures on transient network errors.

Add delete | react to TelegramNetworkErrorContext (additive) and use them at
the two callers. The helper default (context !== "send") is unchanged, so
delete/react now match polling/webhook/unknown. sendMessage keeps "send".

Co-Authored-By: Claude <[email protected]>
@openclaw-barnacle openclaw-barnacle Bot added channel: telegram Channel integration: telegram size: XS labels Jun 25, 2026
@clawsweeper

clawsweeper Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 24, 2026, 9:01 PM ET / 01:01 UTC.

Summary
The PR adds delete and react Telegram retry contexts, uses them in deleteMessageTelegram and reactMessageTelegram, and adds a regression test for snippet-only transient network errors.

PR surface: Source +6, Tests +10. Total +16 across 3 files.

Reproducibility: yes. at source level: current main passes context: "send" for delete/reaction while the helper disables broad snippet matching only for send context. I did not run a live Telegram socket-failure injection, but the predicate and caller path are clear from source.

Review metrics: none identified.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • none.

Next step before merge

  • No ClawSweeper repair is needed; the branch is focused, reviewable, and has no blocking findings.

Security
Cleared: The diff only changes TypeScript retry context labels and a focused unit test; it does not add dependencies, scripts, workflow permissions, credential handling, or new network endpoints.

Review details

Best possible solution:

Land the focused retry-context fix after relevant CI completes, while preserving the strict safe-send policy for non-idempotent Telegram sends.

Do we have a high-confidence way to reproduce the issue?

Yes, at source level: current main passes context: "send" for delete/reaction while the helper disables broad snippet matching only for send context. I did not run a live Telegram socket-failure injection, but the predicate and caller path are clear from source.

Is this the best way to solve the issue?

Yes; separate delete and react contexts are narrower than relaxing send-context matching and keep the duplicate-message guardrails for non-idempotent sends intact. The added predicate test covers the intended behavior boundary.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 8a5cb85c31e5.

Label changes

Label justifications:

  • P2: The PR fixes a bounded Telegram channel reliability bug without touching core runtime, config, migrations, or provider routing.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes copied live terminal output from a Node runtime probe importing the real retry predicate and showing after-fix delete/react behavior against send and polling.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes copied live terminal output from a Node runtime probe importing the real retry predicate and showing after-fix delete/react behavior against send and polling.
Evidence reviewed

PR surface:

Source +6, Tests +10. Total +16 across 3 files.

View PR surface stats
Area Files Added Removed Net
Source 2 9 3 +6
Tests 1 10 0 +10
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 3 19 3 +16

What I checked:

  • Root and scoped policy applied: Root AGENTS.md, extensions/AGENTS.md, and the Telegram maintainer note were read; they require source/test/dependency/history inspection and treat this as a Telegram plugin-owned behavior change. (AGENTS.md:1, 8a5cb85c31e5)
  • Current-main misclassification: Current main builds requestWithDiag for reactMessageTelegram and deleteMessageTelegram with context: "send", so both idempotent operations inherit send-context strictness. (extensions/telegram/src/send.ts:1217, 8a5cb85c31e5)
  • Retry helper semantics: isRecoverableTelegramNetworkError defaults allowMessageMatch to false only when options.context === "send"; any other context allows broad transient message snippets such as undici or socket hang up. (extensions/telegram/src/network-errors.ts:299, 8a5cb85c31e5)
  • Existing regression boundary: The current test suite already preserves the intended send-vs-polling boundary by expecting snippet-only errors to be false in send context and true in polling context. (extensions/telegram/src/network-errors.test.ts:132, 8a5cb85c31e5)
  • PR diff matches the implicated path: The PR changes only the two implicated caller contexts, extends the local context union additively, and adds a regression test that keeps send strict while delete/react retry like polling. (extensions/telegram/src/send.ts:1219, a85f1d59c33c)
  • Retry wrapper preserves non-idempotent safeguards: The shared channel retry runner only suppresses its fallback regex when strictShouldRetry is set; this PR does not touch the existing non-idempotent send helper that uses isSafeToRetrySendError with strict mode. (src/infra/retry-policy.ts:21, 8a5cb85c31e5)

Likely related people:

  • gumadeiras: Commit b861a0b added the Telegram network-error classifier, retry context type, and send-context use across send, reaction, and delete paths. (role: introduced retry context behavior; confidence: high; commits: b861a0bd73e6; files: src/telegram/network-errors.ts, src/telegram/send.ts)
  • steipete: Landed the duplicate-message safe-send fix from the earlier Telegram retry PR, deduped the non-idempotent request helper, and originally introduced the reaction helper path. (role: prior retry-safety author and merger; confidence: high; commits: eb09d8dd716b, f866e57de3d6, 3afef2d504ec; files: src/telegram/send.ts, src/telegram/network-errors.ts, src/infra/retry-policy.ts)
  • sleontenko: Commit 83a25d2 added deleteMessageTelegram and the Telegram message-delete action surface that this PR now adjusts. (role: introduced delete helper; confidence: medium; commits: 83a25d26fc76; files: src/telegram/send.ts)
  • scoootscooob: Commit e5bca08 moved the Telegram channel implementation from src/telegram to extensions/telegram/src, preserving the current files and ownership boundary reviewed here. (role: moved Telegram implementation to plugin boundary; confidence: medium; commits: e5bca0832fbd; files: extensions/telegram/src/send.ts, extensions/telegram/src/network-errors.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal backlog priority with limited blast radius. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels Jun 25, 2026
@vincentkoc
vincentkoc merged commit ce15f34 into openclaw:main Jun 27, 2026
143 of 153 checks passed
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jun 28, 2026
…claw#96612)

reactMessageTelegram and deleteMessageTelegram passed context: "send" to
isRecoverableTelegramNetworkError, which disables message-snippet matching
(allowMessageMatch defaults to false only for "send"). Both operations are
idempotent (setMessageReaction / deleteMessage are safe to repeat), yet a
transient snippet-only network error (e.g. "socket hang up", "undici network
error" with no error code) was not retried — stricter than polling/webhook/
unknown, which all default allowMessageMatch to true. Users saw spurious
reaction/delete failures on transient network errors.

Add delete | react to TelegramNetworkErrorContext (additive) and use them at
the two callers. The helper default (context !== "send") is unchanged, so
delete/react now match polling/webhook/unknown. sendMessage keeps "send".

Co-authored-by: Claude <[email protected]>
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
…claw#96612)

reactMessageTelegram and deleteMessageTelegram passed context: "send" to
isRecoverableTelegramNetworkError, which disables message-snippet matching
(allowMessageMatch defaults to false only for "send"). Both operations are
idempotent (setMessageReaction / deleteMessage are safe to repeat), yet a
transient snippet-only network error (e.g. "socket hang up", "undici network
error" with no error code) was not retried — stricter than polling/webhook/
unknown, which all default allowMessageMatch to true. Users saw spurious
reaction/delete failures on transient network errors.

Add delete | react to TelegramNetworkErrorContext (additive) and use them at
the two callers. The helper default (context !== "send") is unchanged, so
delete/react now match polling/webhook/unknown. sendMessage keeps "send".

Co-authored-by: Claude <[email protected]>
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 1, 2026
…claw#96612)

reactMessageTelegram and deleteMessageTelegram passed context: "send" to
isRecoverableTelegramNetworkError, which disables message-snippet matching
(allowMessageMatch defaults to false only for "send"). Both operations are
idempotent (setMessageReaction / deleteMessage are safe to repeat), yet a
transient snippet-only network error (e.g. "socket hang up", "undici network
error" with no error code) was not retried — stricter than polling/webhook/
unknown, which all default allowMessageMatch to true. Users saw spurious
reaction/delete failures on transient network errors.

Add delete | react to TelegramNetworkErrorContext (additive) and use them at
the two callers. The helper default (context !== "send") is unchanged, so
delete/react now match polling/webhook/unknown. sendMessage keeps "send".

Co-authored-by: Claude <[email protected]>
Rorqualx pushed a commit to Rorqualx/cortex that referenced this pull request Jul 2, 2026
…claw#96612)

reactMessageTelegram and deleteMessageTelegram passed context: "send" to
isRecoverableTelegramNetworkError, which disables message-snippet matching
(allowMessageMatch defaults to false only for "send"). Both operations are
idempotent (setMessageReaction / deleteMessage are safe to repeat), yet a
transient snippet-only network error (e.g. "socket hang up", "undici network
error" with no error code) was not retried — stricter than polling/webhook/
unknown, which all default allowMessageMatch to true. Users saw spurious
reaction/delete failures on transient network errors.

Add delete | react to TelegramNetworkErrorContext (additive) and use them at
the two callers. The helper default (context !== "send") is unchanged, so
delete/react now match polling/webhook/unknown. sendMessage keeps "send".

Co-authored-by: Claude <[email protected]>
(cherry picked from commit ce15f34)
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 3, 2026
…claw#96612)

reactMessageTelegram and deleteMessageTelegram passed context: "send" to
isRecoverableTelegramNetworkError, which disables message-snippet matching
(allowMessageMatch defaults to false only for "send"). Both operations are
idempotent (setMessageReaction / deleteMessage are safe to repeat), yet a
transient snippet-only network error (e.g. "socket hang up", "undici network
error" with no error code) was not retried — stricter than polling/webhook/
unknown, which all default allowMessageMatch to true. Users saw spurious
reaction/delete failures on transient network errors.

Add delete | react to TelegramNetworkErrorContext (additive) and use them at
the two callers. The helper default (context !== "send") is unchanged, so
delete/react now match polling/webhook/unknown. sendMessage keeps "send".

Co-authored-by: Claude <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: telegram Channel integration: telegram P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: XS status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants