Skip to content

fix(telegram): add exponential backoff for sendChatAction 401 to prevent bot deletion#27415

Closed
widingmarcus-cyber wants to merge 3 commits intoopenclaw:mainfrom
widingmarcus-cyber:fix/telegram-sendchataction-401-loop
Closed

fix(telegram): add exponential backoff for sendChatAction 401 to prevent bot deletion#27415
widingmarcus-cyber wants to merge 3 commits intoopenclaw:mainfrom
widingmarcus-cyber:fix/telegram-sendchataction-401-loop

Conversation

@widingmarcus-cyber
Copy link
Contributor

Problem

When a Telegram bot token becomes invalid (401 Unauthorized), the gateway enters an infinite loop of sendChatAction API calls with no backoff. This results in thousands of rapid-fire 401 errors (~1/sec), causing Telegram to flag the bot as abusive and permanently delete it.

Impact

CRITICAL - This bug destroys Telegram bots permanently:

  • 15,899 sendChatAction failures in a single day's log
  • ~85% error rate (16,788 total 401 errors out of 18,594 log lines)
  • Two separate bots destroyed in 48 hours
  • Users must create new bots via @Botfather each time
  • All existing chat history with users is lost

Root Cause

The typing keepalive loop calls sendChatAction every 6 seconds with no error handling for 401 responses. When the token is invalid, each call fails with 401, but the loop continues indefinitely. Telegram interprets the rapid-fire requests as abuse and deletes the bot.

Fix

Two-layer defense:

  1. Exponential backoff: After each consecutive 401, delay increases (1s, 2s, 4s, 8s... up to 5 min)
  2. Circuit breaker: After 10 consecutive 401 failures, suspend sendChatAction entirely and log a clear warning that the bot token is invalid

Testing

  • New test file sendchataction-401-backoff.test.ts with comprehensive coverage
  • All existing Telegram tests pass

Fixes #27092

@widingmarcus-cyber
Copy link
Contributor Author

@greptile-apps please review

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 26, 2026

Greptile Summary

This PR successfully implements a two-layer defense against Telegram bot deletion caused by infinite sendChatAction 401 error loops. The solution creates a global per-account handler that tracks consecutive 401 failures across all chats and applies exponential backoff (1s → 2s → 4s → 8s → up to 5 minutes). After 10 consecutive failures, the circuit breaker suspends all sendChatAction calls with a clear warning message to replace the invalid token.

Key Changes:

  • New sendchataction-401-backoff.ts module with handler factory that tracks failures globally per account
  • Handler properly threaded through bot.tsbot-message.tsbot-message-context.ts call chain
  • Replaces direct bot.api.sendChatAction calls in sendTyping() and sendRecordVoice() with the wrapped handler
  • Comprehensive test suite with 7 test cases covering backoff, suspension, recovery, and global state

Testing:

  • All new tests pass and mock sleepWithAbort to avoid delays
  • Tests verify exponential backoff, circuit breaker activation, success recovery, and cross-chat state sharing
  • Existing Telegram tests pass (per PR description)

The implementation correctly uses existing backoff infrastructure (computeBackoff, sleepWithAbort) and integrates cleanly with the error logging framework.

Confidence Score: 4/5

  • This PR is safe to merge with minimal risk - it adds defensive error handling without changing core message processing logic
  • Score reflects solid implementation, comprehensive tests, and clean integration. Small deduction because this is a defensive fix for an upstream token management issue rather than addressing the root cause. The handler correctly implements exponential backoff and circuit breaker patterns, properly resets on success, and is safely shared across concurrent message contexts.
  • No files require special attention - all changes follow existing patterns and are well-tested

Last reviewed commit: d88da64

@widingmarcus-cyber
Copy link
Contributor Author

@greptile-apps please review — addressed feedback: now uses existing computeBackoff()/sleepWithAbort() from src/infra/backoff.ts instead of custom sleep utility. Removed redundant src/infra/sleep.ts.

Address Greptile review feedback:
- Handler is now created once in createTelegramBot() and shared across
  all message contexts for the account. This means consecutive 401s from
  ANY chat are tracked together, triggering the circuit breaker faster.
- Reuse existing src/infra/backoff.ts (computeBackoff, sleepWithAbort)
  instead of custom sleep utility.
- New test verifies cross-chatId failure counting (global behavior).
- Interface now accepts chatId/action/threadParams per-call instead of
  per-handler, matching the global usage pattern.
@widingmarcus-cyber widingmarcus-cyber force-pushed the fix/telegram-sendchataction-401-loop branch from 31b857b to 6e343e0 Compare February 26, 2026 10:27
@widingmarcus-cyber
Copy link
Contributor Author

Refactored based on Greptile feedback:

  1. Global handler per-account instead of per-message: The sendChatActionHandler is now created once in createTelegramBot() and shared across all message contexts. Consecutive 401s from ANY chat are tracked together, so the circuit breaker triggers faster (10 total, not 10 per-user).

  2. Reuses existing src/infra/backoff.ts: Uses computeBackoff() and sleepWithAbort() instead of custom sleep utility.

  3. New test: Verifies cross-chatId failure counting (global behavior).

@greptile-apps please review

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

4 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

baseRequireMention,
);

const { sendChatActionHandler } = params;
Copy link
Contributor

Choose a reason for hiding this comment

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

params is not defined - this will cause ReferenceError at runtime. The function uses destructuring in its signature, so there's no params variable.

Suggested change
const { sendChatActionHandler } = params;
// sendChatActionHandler is already available from function parameters (needs to be added to destructuring above)
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/telegram/bot-message-context.ts
Line: 245

Comment:
`params` is not defined - this will cause `ReferenceError` at runtime. The function uses destructuring in its signature, so there's no `params` variable.

```suggestion
  // sendChatActionHandler is already available from function parameters (needs to be added to destructuring above)
```

How can I resolve this? If you propose a fix, please make it concise.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 26, 2026

Additional Comments (3)

src/telegram/bot-message-context.ts
sendChatActionHandler is missing from the destructured parameters. It's defined in the type but not extracted here.

export const buildTelegramMessageContext = async ({
  primaryCtx,
  allMedia,
  storeAllowFrom,
  options,
  bot,
  cfg,
  account,
  historyLimit,
  groupHistories,
  dmPolicy,
  allowFrom,
  groupAllowFrom,
  ackReactionScope,
  logger,
  resolveGroupActivation,
  resolveGroupRequireMention,
  resolveTelegramGroupConfig,
  sendChatActionHandler,
}: BuildTelegramMessageContextParams) => {
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/telegram/bot-message-context.ts
Line: 143-161

Comment:
`sendChatActionHandler` is missing from the destructured parameters. It's defined in the type but not extracted here.

```suggestion
export const buildTelegramMessageContext = async ({
  primaryCtx,
  allMedia,
  storeAllowFrom,
  options,
  bot,
  cfg,
  account,
  historyLimit,
  groupHistories,
  dmPolicy,
  allowFrom,
  groupAllowFrom,
  ackReactionScope,
  logger,
  resolveGroupActivation,
  resolveGroupRequireMention,
  resolveTelegramGroupConfig,
  sendChatActionHandler,
}: BuildTelegramMessageContextParams) => {
```

How can I resolve this? If you propose a fix, please make it concise.

src/telegram/bot.ts
The sendChatActionHandler is created later (line 414) but never passed to the message processor. This means the handler is never used, and the 401 backoff fix won't work.

The handler needs to be created before createTelegramMessageProcessor is called, then passed as a parameter. Alternatively, access it via bot.sendChatActionHandler after creating it.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/telegram/bot.ts
Line: 352-372

Comment:
The `sendChatActionHandler` is created later (line 414) but never passed to the message processor. This means the handler is never used, and the 401 backoff fix won't work.

The handler needs to be created before `createTelegramMessageProcessor` is called, then passed as a parameter. Alternatively, access it via `bot.sendChatActionHandler` after creating it.

How can I resolve this? If you propose a fix, please make it concise.

src/telegram/bot-message.ts
sendChatActionHandler is not passed to buildTelegramMessageContext. Even if it's available in deps, it needs to be explicitly passed here.

    const context = await buildTelegramMessageContext({
      primaryCtx,
      allMedia,
      storeAllowFrom,
      options,
      bot,
      cfg,
      account,
      historyLimit,
      groupHistories,
      dmPolicy,
      allowFrom,
      groupAllowFrom,
      ackReactionScope,
      logger,
      resolveGroupActivation,
      resolveGroupRequireMention,
      resolveTelegramGroupConfig,
      sendChatActionHandler: deps.sendChatActionHandler,
    });
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/telegram/bot-message.ts
Line: 55-73

Comment:
`sendChatActionHandler` is not passed to `buildTelegramMessageContext`. Even if it's available in `deps`, it needs to be explicitly passed here.

```suggestion
    const context = await buildTelegramMessageContext({
      primaryCtx,
      allMedia,
      storeAllowFrom,
      options,
      bot,
      cfg,
      account,
      historyLimit,
      groupHistories,
      dmPolicy,
      allowFrom,
      groupAllowFrom,
      ackReactionScope,
      logger,
      resolveGroupActivation,
      resolveGroupRequireMention,
      resolveTelegramGroupConfig,
      sendChatActionHandler: deps.sendChatActionHandler,
    });
```

How can I resolve this? If you propose a fix, please make it concise.

Address Greptile 1/5 review — handler was created but never reached
the actual sendChatAction call sites.

Call chain:
  createTelegramBot() → creates global handler
  → createTelegramMessageProcessor(deps) → receives handler
  → buildTelegramMessageContext(params) → receives handler
  → sendTyping() / sendRecordVoice() → uses handler

Changes:
- Handler created before message processor in bot.ts
- Passed through bot-message.ts deps destructuring
- Required (not optional) in BuildTelegramMessageContextParams
- Removed fallback to raw bot.api.sendChatAction
- All 7 backoff tests pass
@widingmarcus-cyber
Copy link
Contributor Author

Fixed all integration issues from Greptile 1/5 review:

The handler is now properly threaded through the entire call chain:

createTelegramBot()              → creates global handler
  → createTelegramMessageProcessor(deps)  → receives handler in deps  
    → buildTelegramMessageContext(params) → receives handler in params
      → sendTyping() / sendRecordVoice() → uses handler directly

Specific fixes:

  • Handler created BEFORE createTelegramMessageProcessor in bot.ts
  • sendChatActionHandler added to deps destructuring in bot-message.ts
  • Passed through to buildTelegramMessageContext() call
  • Made required (not optional) in BuildTelegramMessageContextParams
  • Removed all fallback to raw bot.api.sendChatAction
  • No undefined variable references

@greptile-apps please review

The handler was in BuildTelegramMessageContextParams but missing from
the actual destructured arguments of buildTelegramMessageContext().
This caused a ReferenceError at runtime since 'params' is not a variable
(the function uses destructuring, not a params object).

Also removed the redundant re-destructuring lower in the function.
@widingmarcus-cyber
Copy link
Contributor Author

Fixed the ReferenceError: sendChatActionHandler was added to the type but missing from the function's destructured parameters.

buildTelegramMessageContext uses destructuring (async ({ bot, cfg, ... })), not a params object. The handler was in the type definition but not in the actual destructured argument list, causing params is not defined at runtime.

Now properly destructured alongside the other parameters.

@greptile-apps please review

steipete added a commit that referenced this pull request Feb 26, 2026
…nks @widingmarcus-cyber)

Co-authored-by: Marcus Widing <widing.marcus@gmail.com>
@steipete
Copy link
Contributor

Landed on main via commit b096ad267.

What was landed from this PR:

  • Added per-account global sendChatAction handler with 401-aware exponential backoff and suspension guard.
  • Routed Telegram typing calls through the new handler in message context + bot wiring.
  • Added focused regression coverage in sendchataction-401-backoff.test.ts.
  • Added changelog entry under 2026.2.26 (Unreleased) with attribution.

Original PR commits:

  • 6e343e038beee7b9d71e30c9bd1a074603f0d9e0
  • 7d604709d6675fb664e3934df5fdcda9ab63c203
  • d88da64f965af7848e92a2e63038b66830790153

Landing integration note:

  • Included a type-safe cast at Telegram bot integration (sendChatAction thread params) required to pass current strict TypeScript checks on main.

Validation run before landing (/landpr flow):

  • pnpm lint: pass
  • pnpm build: pass
  • pnpm test: pass

Thanks @widingmarcus-cyber for the incident-grade fix.

@steipete steipete closed this Feb 26, 2026
robbyczgw-cla pushed a commit to robbyczgw-cla/openclaw that referenced this pull request Feb 26, 2026
, thanks @widingmarcus-cyber)

Co-authored-by: Marcus Widing <widing.marcus@gmail.com>
execute008 pushed a commit to execute008/openclaw that referenced this pull request Feb 27, 2026
, thanks @widingmarcus-cyber)

Co-authored-by: Marcus Widing <widing.marcus@gmail.com>
r4jiv007 pushed a commit to r4jiv007/openclaw that referenced this pull request Feb 28, 2026
, thanks @widingmarcus-cyber)

Co-authored-by: Marcus Widing <widing.marcus@gmail.com>
vincentkoc pushed a commit to Sid-Qin/openclaw that referenced this pull request Feb 28, 2026
, thanks @widingmarcus-cyber)

Co-authored-by: Marcus Widing <widing.marcus@gmail.com>
vincentkoc pushed a commit to rylena/rylen-openclaw that referenced this pull request Feb 28, 2026
, thanks @widingmarcus-cyber)

Co-authored-by: Marcus Widing <widing.marcus@gmail.com>
CyberK13 pushed a commit to CyberK13/clawdbot that referenced this pull request Mar 1, 2026
, thanks @widingmarcus-cyber)

Co-authored-by: Marcus Widing <widing.marcus@gmail.com>
steipete added a commit to Sid-Qin/openclaw that referenced this pull request Mar 2, 2026
, thanks @widingmarcus-cyber)

Co-authored-by: Marcus Widing <widing.marcus@gmail.com>
robertchang-ga pushed a commit to robertchang-ga/openclaw that referenced this pull request Mar 2, 2026
, thanks @widingmarcus-cyber)

Co-authored-by: Marcus Widing <widing.marcus@gmail.com>
dorgonman pushed a commit to kanohorizonia/openclaw that referenced this pull request Mar 3, 2026
, thanks @widingmarcus-cyber)

Co-authored-by: Marcus Widing <widing.marcus@gmail.com>
zooqueen pushed a commit to hanzoai/bot that referenced this pull request Mar 6, 2026
, thanks @widingmarcus-cyber)

Co-authored-by: Marcus Widing <widing.marcus@gmail.com>
thebenjaminlee pushed a commit to escape-velocity-ventures/openclaw that referenced this pull request Mar 7, 2026
, thanks @widingmarcus-cyber)

Co-authored-by: Marcus Widing <widing.marcus@gmail.com>
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: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Telegram gateway sends unbounded sendChatAction requests on 401, causing Telegram to delete bots

2 participants