openclaw - ✅(Solved) Fix [Bug]: channels.telegram.pollingStallThresholdMs silently ignored when isolated polling ingress is enabled (default) [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#83950Fetched 2026-05-20 03:46:10
View on GitHub
Comments
1
Participants
2
Timeline
19
Reactions
1
Author
Timeline (top)
referenced ×9labeled ×6cross-referenced ×2commented ×1

channels.telegram.pollingStallThresholdMs is silently ignored when the isolated polling ingress is active (the default since 2026.5.12). The setting is read, validated, clamped, stored on TelegramPollingSession, and then never consulted in the isolated code path — only #runPollingCycle uses it. On a hosted gateway this manifests as a configured "5-minute polling stall watchdog" that never fires.

Error Message

The relevant evidence is structural: grep -n "this.#stallThresholdMs" extensions/telegram/src/polling-session.ts returns exactly two hits — the constructor assignment and the #runPollingCycle watchdog. No isolated-cycle hit. That is the whole bug.

Root Cause

  1. Set channels.telegram.pollingStallThresholdMs: 300000 in openclaw.json.
  2. Start the gateway with default Telegram polling (no webhook, no explicit isolatedIngress.enabled = false — none is exposed in config or CLI today; see MonitorTelegramOpts and monitorTelegramProvider call site in extensions/telegram/src/channel.ts / probe.ts).
  3. TelegramPollingSession.runUntilAbort reaches #runIsolatedIngressCycle(bot) because this.opts.isolatedIngress?.enabled defaults to true in probe.ts:
    isolatedIngress: {
      enabled: opts.isolatedIngress?.enabled ?? true,
      // ...
    },
  4. Grep #runIsolatedIngressCycle for this.#stallThresholdMs — it is not referenced anywhere in the isolated cycle. The watchdog setInterval(...) that calls liveness.detectStall({ thresholdMs: this.#stallThresholdMs }) lives only in #runPollingCycle.

Fix Action

Fix / Workaround

Live(ish) corroboration on 2026.5.12 (f066dd2): after an upstream IPv4-only sticky fetch dispatcher swap recovered, our isolated ingress went silent for ~11 minutes (no poll-success, no poll-error, no log line). update-offset-default.json mtime stayed frozen at the moment of the swap; pending_update_count from getWebhookInfo rose from 0 → 1 when the user sent a fresh DM; no stall log was emitted; systemctl --user restart openclaw-gateway recovered it instantly. The 11-min wedge here may itself be #83272 / #83505 (drain side, fixed in 2026.5.19) rather than the worker, so I am intentionally not filing this as the same incident — this issue is the source-level fact that the configured watchdog setting is silently ignored regardless of which side wedged.

A local external systemd timer + getWebhookInfo + update-offset-default.json mtime check is a workable mitigation today and is what I've deployed; happy to share that script if useful, but a built-in honor of the already-documented config is the right durable fix.

PR fix notes

PR #84013: fix(telegram): honor pollingStallThresholdMs in isolated ingress mode

Description (problem / solution / changelog)

Summary

The configured pollingStallThresholdMs was only consulted in the non-isolated polling cycle (#runPollingCycle). The isolated ingress path — which has been the default since 2026.5.12 — had no watchdog, so a wedged worker would never trigger a restart regardless of the configured threshold.

Root Cause

TelegramPollingSession stores #stallThresholdMs at construction (line 234) but only references it at line 903 inside #runPollingCycle. The #runIsolatedIngressCycle path (line 661) receives poll-start, poll-success, and poll-error messages from the worker but never feeds them to a liveness tracker or checks for staleness.

Fix

Add a parent-side TelegramPollingLivenessTracker to #runIsolatedIngressCycle() that:

  1. Feeds on existing worker poll-start/poll-success/poll-error messages
  2. Runs a POLL_WATCHDOG_INTERVAL_MS interval that calls detectStall({ thresholdMs })
  3. On stall: marks transport dirty, sets restartRequested, logs, and stops the worker
  4. Cleans up the watchdog in the finally block

This mirrors the existing stall detection in #runPollingCycle (lines 897–923) and reuses the same TelegramPollingLivenessTracker class and POLL_WATCHDOG_INTERVAL_MS constant.

Tests

3 new tests in polling-session.test.ts:

  • Stall detection fires when worker sends no messages
  • Healthy polling does not trigger false restart
  • Custom stallThresholdMs value is honored in isolated mode

All 45 existing + new polling session tests pass.

Fixes #83950

Changed files

  • extensions/telegram/src/polling-session.test.ts (modified, +219/-0)
  • extensions/telegram/src/polling-session.ts (modified, +18/-0)

Code Example

isolatedIngress: {
     enabled: opts.isolatedIngress?.enabled ?? true,
     // ...
   },

---

this.#stallThresholdMs = resolvePollingStallThresholdMs(opts.stallThresholdMs);

---

if (
  (this.opts.isolatedIngress?.enabled
    ? await this.#runIsolatedIngressCycle(bot)
    : await this.#runPollingCycle(bot)) === "exit"
) return;

---

const watchdog = setInterval(() => {
  if (this.opts.abortSignal?.aborted) return;
  const stall = liveness.detectStall({ thresholdMs: this.#stallThresholdMs });
  // ...
}, POLL_WATCHDOG_INTERVAL_MS);
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

channels.telegram.pollingStallThresholdMs is silently ignored when the isolated polling ingress is active (the default since 2026.5.12). The setting is read, validated, clamped, stored on TelegramPollingSession, and then never consulted in the isolated code path — only #runPollingCycle uses it. On a hosted gateway this manifests as a configured "5-minute polling stall watchdog" that never fires.

Steps to reproduce

Source-level, against main (extensions/telegram/src/polling-session.ts, ~SHA 8bd24ad):

  1. Set channels.telegram.pollingStallThresholdMs: 300000 in openclaw.json.
  2. Start the gateway with default Telegram polling (no webhook, no explicit isolatedIngress.enabled = false — none is exposed in config or CLI today; see MonitorTelegramOpts and monitorTelegramProvider call site in extensions/telegram/src/channel.ts / probe.ts).
  3. TelegramPollingSession.runUntilAbort reaches #runIsolatedIngressCycle(bot) because this.opts.isolatedIngress?.enabled defaults to true in probe.ts:
    isolatedIngress: {
      enabled: opts.isolatedIngress?.enabled ?? true,
      // ...
    },
  4. Grep #runIsolatedIngressCycle for this.#stallThresholdMs — it is not referenced anywhere in the isolated cycle. The watchdog setInterval(...) that calls liveness.detectStall({ thresholdMs: this.#stallThresholdMs }) lives only in #runPollingCycle.

Live(ish) corroboration on 2026.5.12 (f066dd2): after an upstream IPv4-only sticky fetch dispatcher swap recovered, our isolated ingress went silent for ~11 minutes (no poll-success, no poll-error, no log line). update-offset-default.json mtime stayed frozen at the moment of the swap; pending_update_count from getWebhookInfo rose from 0 → 1 when the user sent a fresh DM; no stall log was emitted; systemctl --user restart openclaw-gateway recovered it instantly. The 11-min wedge here may itself be #83272 / #83505 (drain side, fixed in 2026.5.19) rather than the worker, so I am intentionally not filing this as the same incident — this issue is the source-level fact that the configured watchdog setting is silently ignored regardless of which side wedged.

Expected behavior

One of:

  1. channels.telegram.pollingStallThresholdMs is honored by the isolated path — e.g. a parent-side timer on the worker.task() await that forces a worker.stop() + cycle restart when no poll-start / poll-success / poll-error / spooled message has arrived from the worker for stallThresholdMs, or
  2. The setting is documented to apply only when isolated ingress is disabled, and channels.telegram.isolatedIngress.enabled (or equivalent) is exposed in config / CLI / MonitorTelegramOpts so operators can opt out and recover the existing watchdog semantics from #runPollingCycle.

Actual behavior

TelegramPollingSession constructor:

this.#stallThresholdMs = resolvePollingStallThresholdMs(opts.stallThresholdMs);

is invoked, the value is clamped to [30_000, 600_000], then in runUntilAbort:

if (
  (this.opts.isolatedIngress?.enabled
    ? await this.#runIsolatedIngressCycle(bot)
    : await this.#runPollingCycle(bot)) === "exit"
) return;

Only the embedded branch installs the watchdog:

const watchdog = setInterval(() => {
  if (this.opts.abortSignal?.aborted) return;
  const stall = liveness.detectStall({ thresholdMs: this.#stallThresholdMs });
  // ...
}, POLL_WATCHDOG_INTERVAL_MS);

The isolated branch awaits worker.task() with no equivalent timer. Even after #83505 (drain-side tombstones in 2026.5.19), no code path consumes this.#stallThresholdMs if the worker side ever becomes silently unresponsive (e.g. event-loop starvation in the worker, OS-level socket wedge, or any future inactivity mode that bot.api.config.use style instrumentation does not cover).

Net effect: an operator who sets pollingStallThresholdMs: 300000 (or whatever) gets the same behavior as if the field were absent. No warning, no doctor diagnostic, no log line. The TUI / Web UI and openclaw status likewise present no signal that the setting is inert.

OpenClaw version

2026.5.12 (f066dd2) (install observed). Source review against main at commit 8bd24ad confirms the gap is still present in 2026.5.19 / 5.20-betas — the recently-shipped #83505 fix added drain-side recovery but did not wire this.#stallThresholdMs into #runIsolatedIngressCycle.

Operating system

Linux 6.17 (Ubuntu user systemd)

Install method

npm i -g openclaw

Node.js version

(Whatever the global install of 2026.5.12 runs against — not relevant; this is a source-level claim.)

Channel(s) affected

  • Telegram (polling mode, isolated ingress = default-on)

Logs

The relevant evidence is structural: grep -n "this.#stallThresholdMs" extensions/telegram/src/polling-session.ts returns exactly two hits — the constructor assignment and the #runPollingCycle watchdog. No isolated-cycle hit. That is the whole bug.

Proposed fix shape

Minimal, low-risk: in #runIsolatedIngressCycle, track lastWorkerMessageAt on each poll-start/poll-success/poll-error/spooled event; install a setInterval(POLL_WATCHDOG_INTERVAL_MS) that calls worker.stop() and returns "continue" from the cycle when Date.now() - lastWorkerMessageAt > this.#stallThresholdMs (mirroring the embedded path's stalledRestart handling and forceCycleTimer grace).

Belt-and-suspenders: also expose channels.telegram.isolatedIngress.enabled in the schema and monitorTelegramProvider call chain so the embedded path remains accessible to operators who prefer the older, single-threaded behavior.

Additional context

A local external systemd timer + getWebhookInfo + update-offset-default.json mtime check is a workable mitigation today and is what I've deployed; happy to share that script if useful, but a built-in honor of the already-documented config is the right durable fix.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

One of:

  1. channels.telegram.pollingStallThresholdMs is honored by the isolated path — e.g. a parent-side timer on the worker.task() await that forces a worker.stop() + cycle restart when no poll-start / poll-success / poll-error / spooled message has arrived from the worker for stallThresholdMs, or
  2. The setting is documented to apply only when isolated ingress is disabled, and channels.telegram.isolatedIngress.enabled (or equivalent) is exposed in config / CLI / MonitorTelegramOpts so operators can opt out and recover the existing watchdog semantics from #runPollingCycle.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix [Bug]: channels.telegram.pollingStallThresholdMs silently ignored when isolated polling ingress is enabled (default) [1 pull requests, 1 comments, 2 participants]