fix(telegram): lower polling keepalive delay#83304
Conversation
|
Codex review: needs real behavior proof before merge. Reviewed May 28, 2026, 12:37 AM ET / 04:37 UTC. Summary PR surface: Source +4, Tests +8. Total +12 across 2 files. Reproducibility: no. high-confidence current-main reproduction path was established. Source inspection and undici 8.3.0 source show current main relies on the 60s default keepalive delay, but the PR discussion says controlled NAT expiry and final-head live Telegram deploy were not tested. Review metrics: 1 noteworthy metric.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Mantis proof suggestion Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the narrowed Telegram-only dispatcher change after final-head live Telegram polling proof is supplied or maintainers explicitly accept the transport risk. Do we have a high-confidence way to reproduce the issue? No high-confidence current-main reproduction path was established. Source inspection and undici 8.3.0 source show current main relies on the 60s default keepalive delay, but the PR discussion says controlled NAT expiry and final-head live Telegram deploy were not tested. Is this the best way to solve the issue? Yes for code shape: the narrowed Telegram dispatcher change is more maintainable than the earlier gateway grace and health-policy changes. The remaining blocker is proof and merge-risk handling, not a different implementation direction. AGENTS.md: found and applied where relevant. Codex review notes: model gpt-5.5, reasoning high; reviewed against 647e18aa04a2. Label changesLabel justifications:
Evidence reviewedPR surface: Source +4, Tests +8. Total +12 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
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
|
There was a problem hiding this comment.
Pull request overview
Addresses Telegram long-poll stalls and spurious "disconnected" channel restarts. Adds OS-level TCP keepalive to undici's connect options for api.telegram.org so NAT timeouts and dead sockets surface promptly, stops the polling status publisher from clobbering connected: false at every cycle start, and widens the shared channel connect-grace window from 120s to 300s so slow-handshake channels aren't flagged disconnected before they finish connecting.
Changes:
- Always set
keepAlive: true/keepAliveInitialDelay: 30_000on the Telegram undici Agent connect options. - Remove the
connected: falsewrite fromnotePollingStart; onlynotePollingStop/notePollingErrormark the channel disconnected. - Bump
DEFAULT_CHANNEL_CONNECT_GRACE_MSfrom 120s to 300s and update related Telegram tests.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| extensions/telegram/src/fetch.ts | Enables undici TCP keepalive on the Telegram getUpdates transport; returns connect opts unconditionally. |
| extensions/telegram/src/polling-status.ts | Stops notePollingStart from publishing connected: false; keeps telemetry-only reset. |
| extensions/telegram/src/polling-status.test.ts | Adds regression test asserting notePollingStart patch omits connected. |
| extensions/telegram/src/polling-session.test.ts | Updates expectations: one (not two) connected:false patch per stall+recover cycle; verifies cycle-start telemetry reset. |
| src/gateway/channel-health-policy.ts | Widens default channel connect grace from 120s to 300s with motivating comment. |
98ed39b to
c97dca4
Compare
…nnected:true The widened 300s channel connect grace and the removal of connected:false from notePollingStart left a path where a polling restart could hang forever looking healthy. notePollingStart clears lastConnectedAt, lastEventAt, and lastTransportActivityAt but deliberately omits connected, so server-channels' patch-merge inherits a connected:true from the previous lifecycle. After grace, evaluateChannelHealth's stale-socket branch requires lastTransportActivityAt to be non-null and the connected:false branch is masked, so the channel sits healthy with no first getUpdates. Add a post-grace branch to evaluateChannelHealth that flags polling channels as stale-socket when connected:true is paired with null lastConnectedAt and null lastTransportActivityAt and a non-null lastStartAt. Scoped to mode:polling so webhook channels and channels without continuous transport tracking are not falsely flagged. Align TELEGRAM_POLLING_CONNECT_GRACE_MS in the Telegram status diagnostic with DEFAULT_CHANNEL_CONNECT_GRACE_MS so openclaw channels status agrees with the shared health monitor on the grace window. Refresh the notePollingStart comment to point at the new evaluateChannelHealth branch. Addresses clawsweeper review on openclaw#83304 (P1 connect-grace startup-hang, P2 diagnostic grace drift). Tests cover the new flagged path, the in-grace happy path, and the prior-successful-connect happy path.
|
Addressed both findings in 835553c ( P1 (preserve startup liveness after reconnects): added a post-grace branch to P2 (align Telegram status grace with health grace): set Tests added in
Acceptance criteria run locally and green: |
…ent NAT timeout stalls Long-polling connections to api.telegram.org stay idle for up to the getUpdates timeout (~900 s). Most home/office NAT tables expire idle TCP entries after 60–1800 s (commonly ~1000 s). When the NAT entry is silently dropped the connection hangs rather than returning an error, leaving the grammY runner stuck until the 90 s stall watchdog fires and forces a restart cycle. Fix: unconditionally set `keepAlive: true` and `keepAliveInitialDelay: 30_000` (30 s) on the undici Agent `connect` options built in `buildTelegramConnectOptions`. OS-level TCP keepalive probes sent every ~75 s (OS default) will: 1. Refresh the NAT table entry before it expires. 2. Surface dead connections immediately with ETIMEDOUT instead of hanging forever. The `return Object.keys(connect).length > 0 ? connect : null` guard is also removed; `connect` is now always non-empty so it always returns the object. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> (cherry picked from commit 92e454c)
…iden channel connect grace to 300s (cherry picked from commit 1ca963a)
…nnected:true The widened 300s channel connect grace and the removal of connected:false from notePollingStart left a path where a polling restart could hang forever looking healthy. notePollingStart clears lastConnectedAt, lastEventAt, and lastTransportActivityAt but deliberately omits connected, so server-channels' patch-merge inherits a connected:true from the previous lifecycle. After grace, evaluateChannelHealth's stale-socket branch requires lastTransportActivityAt to be non-null and the connected:false branch is masked, so the channel sits healthy with no first getUpdates. Add a post-grace branch to evaluateChannelHealth that flags polling channels as stale-socket when connected:true is paired with null lastConnectedAt and null lastTransportActivityAt and a non-null lastStartAt. Scoped to mode:polling so webhook channels and channels without continuous transport tracking are not falsely flagged. Align TELEGRAM_POLLING_CONNECT_GRACE_MS in the Telegram status diagnostic with DEFAULT_CHANNEL_CONNECT_GRACE_MS so openclaw channels status agrees with the shared health monitor on the grace window. Refresh the notePollingStart comment to point at the new evaluateChannelHealth branch. Addresses clawsweeper review on openclaw#83304 (P1 connect-grace startup-hang, P2 diagnostic grace drift). Tests cover the new flagged path, the in-grace happy path, and the prior-successful-connect happy path.
…ing startups Defense in depth on top of 87db46c's notePollingStart connected:false fix. The primary path (notePollingStart writes connected:false explicitly so evaluateChannelHealth's existing connected===false branch catches a hung restart) is unchanged. This adds a defensive post-grace branch that catches the same hang via a different signature -- inherited connected:true paired with null lastConnectedAt and null lastTransportActivityAt -- in case a future code path forgets to clear the inherited connected flag on lifecycle start. Scoped to mode:polling so webhook channels and channels without continuous transport tracking are not falsely flagged. Also bump lastStartAt: Date.now() - 121_000 to 301_000 in the spool-handler timeout test added by upstream openclaw#83505 so it falls past the widened 300s TELEGRAM_POLLING_CONNECT_GRACE_MS suppression window (mirroring the same fixup already applied to the two adjacent polling-startup tests).
Drop the 120s -> 300s widening from this PR after maintainer feedback that the extra grace masks real startup bugs. The defense-in-depth checks added in earlier commits (notePollingStart clearing inherited connected state, the stale-socket policy branch, the per-snapshot startup grace test) all work fine at 120s and remain valuable on their own. Reverts in: - src/gateway/channel-health-policy.ts: DEFAULT_CHANNEL_CONNECT_GRACE_MS 300 -> 120 - extensions/telegram/src/status-issues.ts: TELEGRAM_POLLING_CONNECT_GRACE_MS 300 -> 120 - extensions/telegram/src/status.test.ts: lastStartAt 301_000 -> 121_000 (3 cases) The new channel-health-policy.test.ts cases use explicit channelConnectGraceMs: 10_000 in the policy, so they are unaffected by the default constant change.
d31d6b4 to
6462fff
Compare
|
Landed via squash onto main.
Thanks @amittell! |
* fix(telegram): enable TCP keepalive on getUpdates connections to prevent NAT timeout stalls Long-polling connections to api.telegram.org stay idle for up to the getUpdates timeout (~900 s). Most home/office NAT tables expire idle TCP entries after 60–1800 s (commonly ~1000 s). When the NAT entry is silently dropped the connection hangs rather than returning an error, leaving the grammY runner stuck until the 90 s stall watchdog fires and forces a restart cycle. Fix: unconditionally set `keepAlive: true` and `keepAliveInitialDelay: 30_000` (30 s) on the undici Agent `connect` options built in `buildTelegramConnectOptions`. OS-level TCP keepalive probes sent every ~75 s (OS default) will: 1. Refresh the NAT table entry before it expires. 2. Surface dead connections immediately with ETIMEDOUT instead of hanging forever. The `return Object.keys(connect).length > 0 ? connect : null` guard is also removed; `connect` is now always non-empty so it always returns the object. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> (cherry picked from commit 92e454c) * fix(telegram): stop self-flagging disconnected on poll-cycle start; widen channel connect grace to 300s (cherry picked from commit 1ca963a) * fix(telegram): catch hung polling startups that preserve inherited connected:true The widened 300s channel connect grace and the removal of connected:false from notePollingStart left a path where a polling restart could hang forever looking healthy. notePollingStart clears lastConnectedAt, lastEventAt, and lastTransportActivityAt but deliberately omits connected, so server-channels' patch-merge inherits a connected:true from the previous lifecycle. After grace, evaluateChannelHealth's stale-socket branch requires lastTransportActivityAt to be non-null and the connected:false branch is masked, so the channel sits healthy with no first getUpdates. Add a post-grace branch to evaluateChannelHealth that flags polling channels as stale-socket when connected:true is paired with null lastConnectedAt and null lastTransportActivityAt and a non-null lastStartAt. Scoped to mode:polling so webhook channels and channels without continuous transport tracking are not falsely flagged. Align TELEGRAM_POLLING_CONNECT_GRACE_MS in the Telegram status diagnostic with DEFAULT_CHANNEL_CONNECT_GRACE_MS so openclaw channels status agrees with the shared health monitor on the grace window. Refresh the notePollingStart comment to point at the new evaluateChannelHealth branch. Addresses clawsweeper review on openclaw#83304 (P1 connect-grace startup-hang, P2 diagnostic grace drift). Tests cover the new flagged path, the in-grace happy path, and the prior-successful-connect happy path. * fix(telegram): clear polling connected state on startup * fix(gateway): add defense-in-depth health-policy branch for hung polling startups Defense in depth on top of 87db46c's notePollingStart connected:false fix. The primary path (notePollingStart writes connected:false explicitly so evaluateChannelHealth's existing connected===false branch catches a hung restart) is unchanged. This adds a defensive post-grace branch that catches the same hang via a different signature -- inherited connected:true paired with null lastConnectedAt and null lastTransportActivityAt -- in case a future code path forgets to clear the inherited connected flag on lifecycle start. Scoped to mode:polling so webhook channels and channels without continuous transport tracking are not falsely flagged. Also bump lastStartAt: Date.now() - 121_000 to 301_000 in the spool-handler timeout test added by upstream openclaw#83505 so it falls past the widened 300s TELEGRAM_POLLING_CONNECT_GRACE_MS suppression window (mirroring the same fixup already applied to the two adjacent polling-startup tests). * revert(telegram,gateway): keep connect grace at 120s Drop the 120s -> 300s widening from this PR after maintainer feedback that the extra grace masks real startup bugs. The defense-in-depth checks added in earlier commits (notePollingStart clearing inherited connected state, the stale-socket policy branch, the per-snapshot startup grace test) all work fine at 120s and remain valuable on their own. Reverts in: - src/gateway/channel-health-policy.ts: DEFAULT_CHANNEL_CONNECT_GRACE_MS 300 -> 120 - extensions/telegram/src/status-issues.ts: TELEGRAM_POLLING_CONNECT_GRACE_MS 300 -> 120 - extensions/telegram/src/status.test.ts: lastStartAt 301_000 -> 121_000 (3 cases) The new channel-health-policy.test.ts cases use explicit channelConnectGraceMs: 10_000 in the policy, so they are unaffected by the default constant change. * fix(telegram): narrow polling keepalive fix --------- Co-authored-by: Yibei Ou <yibeiou@Yibeis-Mac-mini.local> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(telegram): enable TCP keepalive on getUpdates connections to prevent NAT timeout stalls Long-polling connections to api.telegram.org stay idle for up to the getUpdates timeout (~900 s). Most home/office NAT tables expire idle TCP entries after 60–1800 s (commonly ~1000 s). When the NAT entry is silently dropped the connection hangs rather than returning an error, leaving the grammY runner stuck until the 90 s stall watchdog fires and forces a restart cycle. Fix: unconditionally set `keepAlive: true` and `keepAliveInitialDelay: 30_000` (30 s) on the undici Agent `connect` options built in `buildTelegramConnectOptions`. OS-level TCP keepalive probes sent every ~75 s (OS default) will: 1. Refresh the NAT table entry before it expires. 2. Surface dead connections immediately with ETIMEDOUT instead of hanging forever. The `return Object.keys(connect).length > 0 ? connect : null` guard is also removed; `connect` is now always non-empty so it always returns the object. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> (cherry picked from commit 92e454c) * fix(telegram): stop self-flagging disconnected on poll-cycle start; widen channel connect grace to 300s (cherry picked from commit 1ca963a) * fix(telegram): catch hung polling startups that preserve inherited connected:true The widened 300s channel connect grace and the removal of connected:false from notePollingStart left a path where a polling restart could hang forever looking healthy. notePollingStart clears lastConnectedAt, lastEventAt, and lastTransportActivityAt but deliberately omits connected, so server-channels' patch-merge inherits a connected:true from the previous lifecycle. After grace, evaluateChannelHealth's stale-socket branch requires lastTransportActivityAt to be non-null and the connected:false branch is masked, so the channel sits healthy with no first getUpdates. Add a post-grace branch to evaluateChannelHealth that flags polling channels as stale-socket when connected:true is paired with null lastConnectedAt and null lastTransportActivityAt and a non-null lastStartAt. Scoped to mode:polling so webhook channels and channels without continuous transport tracking are not falsely flagged. Align TELEGRAM_POLLING_CONNECT_GRACE_MS in the Telegram status diagnostic with DEFAULT_CHANNEL_CONNECT_GRACE_MS so openclaw channels status agrees with the shared health monitor on the grace window. Refresh the notePollingStart comment to point at the new evaluateChannelHealth branch. Addresses clawsweeper review on #83304 (P1 connect-grace startup-hang, P2 diagnostic grace drift). Tests cover the new flagged path, the in-grace happy path, and the prior-successful-connect happy path. * fix(telegram): clear polling connected state on startup * fix(gateway): add defense-in-depth health-policy branch for hung polling startups Defense in depth on top of 87db46c's notePollingStart connected:false fix. The primary path (notePollingStart writes connected:false explicitly so evaluateChannelHealth's existing connected===false branch catches a hung restart) is unchanged. This adds a defensive post-grace branch that catches the same hang via a different signature -- inherited connected:true paired with null lastConnectedAt and null lastTransportActivityAt -- in case a future code path forgets to clear the inherited connected flag on lifecycle start. Scoped to mode:polling so webhook channels and channels without continuous transport tracking are not falsely flagged. Also bump lastStartAt: Date.now() - 121_000 to 301_000 in the spool-handler timeout test added by upstream #83505 so it falls past the widened 300s TELEGRAM_POLLING_CONNECT_GRACE_MS suppression window (mirroring the same fixup already applied to the two adjacent polling-startup tests). * revert(telegram,gateway): keep connect grace at 120s Drop the 120s -> 300s widening from this PR after maintainer feedback that the extra grace masks real startup bugs. The defense-in-depth checks added in earlier commits (notePollingStart clearing inherited connected state, the stale-socket policy branch, the per-snapshot startup grace test) all work fine at 120s and remain valuable on their own. Reverts in: - src/gateway/channel-health-policy.ts: DEFAULT_CHANNEL_CONNECT_GRACE_MS 300 -> 120 - extensions/telegram/src/status-issues.ts: TELEGRAM_POLLING_CONNECT_GRACE_MS 300 -> 120 - extensions/telegram/src/status.test.ts: lastStartAt 301_000 -> 121_000 (3 cases) The new channel-health-policy.test.ts cases use explicit channelConnectGraceMs: 10_000 in the policy, so they are unaffected by the default constant change. * fix(telegram): narrow polling keepalive fix --------- Co-authored-by: Yibei Ou <yibeiou@Yibeis-Mac-mini.local> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Ayaan Zaidi <hi@obviy.us>
…026.5.28) (#759) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/openclaw/openclaw](https://openclaw.ai) ([source](https://github.com/openclaw/openclaw)) | patch | `2026.5.27` → `2026.5.28` | --- ### Release Notes <details> <summary>openclaw/openclaw (ghcr.io/openclaw/openclaw)</summary> ### [`v2026.5.28`](https://github.com/openclaw/openclaw/blob/HEAD/CHANGELOG.md#2026528) [Compare Source](openclaw/openclaw@v2026.5.27...v2026.5.28) ##### Highlights - Agent and Codex runtime recovery is steadier: subagents keep cwd/workspace separation, hook context stays prompt-local, session locks release on timeout abort while live OpenClaw locks survive cleanup, stale restart continuations are avoided, and Codex app-server/helper failures no longer tear down shared runtime state. ([#​87218](openclaw/openclaw#87218), [#​86875](openclaw/openclaw#86875), [#​87409](openclaw/openclaw#87409), [#​87399](openclaw/openclaw#87399), [#​87375](openclaw/openclaw#87375), [#​88129](openclaw/openclaw#88129)) - Channel delivery and session identity got safer across outbound plugin hooks, Matrix room ids, iMessage reactions/approvals, Slack final replies, Discord recovered tool warnings, runtime-config message actions, WhatsApp profile auth roots, Telegram polling, and Microsoft Teams service URL trust checks. ([#​73706](openclaw/openclaw#73706), [#​75670](openclaw/openclaw#75670), [#​87366](openclaw/openclaw#87366), [#​87451](openclaw/openclaw#87451), [#​87334](openclaw/openclaw#87334), [#​84535](openclaw/openclaw#84535), [#​82492](openclaw/openclaw#82492), [#​83304](openclaw/openclaw#83304), [#​87160](openclaw/openclaw#87160)) - Mobile and chat surfaces got a broader refresh: the iOS Pro UI, hosted push relay default, realtime Talk tab playback, Gateway chat transport, onboarding, Talk permissions, WebChat reconnect delivery, and session picker behavior now preserve more state across reconnects and empty searches. ([#​87367](openclaw/openclaw#87367), [#​87531](openclaw/openclaw#87531), [#​87682](openclaw/openclaw#87682), [#​88096](openclaw/openclaw#88096), [#​88105](openclaw/openclaw#88105)) Thanks [@​ngutman](https://github.com/ngutman) and [@​BunsDev](https://github.com/BunsDev). - Browser, channel, and automation inputs are stricter: Browser tool timeouts, viewport/tab indices, Gateway ports, cron retry handling, Discord component ids, schema array refs, Telegram callback pages, and channel progress callbacks now reject malformed values earlier and preserve the intended delivery context. ([#​82887](openclaw/openclaw#82887)) - Provider, media, and document coverage expands with Claude Opus 4.8, Fal Krea image schemas, NVIDIA featured models, MiniMax streaming music responses, encrypted PDF extraction, voice model catalogs, GitHub Copilot agent runtime support, and a Codex Supervisor plugin path for delegated Codex workflows. ([#​87845](openclaw/openclaw#87845), [#​87890](openclaw/openclaw#87890), [#​80775](openclaw/openclaw#80775), [#​84764](openclaw/openclaw#84764), [#​87751](openclaw/openclaw#87751), [#​87794](openclaw/openclaw#87794)) - CLI, auth, doctor, and provider paths fail faster and recover more clearly: malformed numeric/version options are rejected, workspace dotenv provider credentials are ignored, heartbeat defaults, OAuth/token lifetimes, and local service startup requests are bounded, agent auth health labels are clearer, legacy `api_key` auth profiles migrate to canonical form, and restart guidance is actionable. ([#​87398](openclaw/openclaw#87398), [#​86281](openclaw/openclaw#86281), [#​87361](openclaw/openclaw#87361), [#​88133](openclaw/openclaw#88133), [#​83655](openclaw/openclaw#83655), [#​87559](openclaw/openclaw#87559), [#​88088](openclaw/openclaw#88088), [#​85924](openclaw/openclaw#85924)) Thanks [@​vincentkoc](https://github.com/vincentkoc) and [@​giodl73-repo](https://github.com/giodl73-repo). - Plugin and Gateway hot paths do less repeated work while preserving cache correctness for install records, config JSON parsing, tool search catalogs, session stores, manifest model rows, auto-enabled plugin config, browser tokens, viewer assets, and release-split external plugin packages. ([#​86699](openclaw/openclaw#86699)) - Release, QA, and E2E validation now bound more log, artifact, harness, and cross-OS waits so failing lanes produce proof instead of hanging or false-greening. ##### Changes - Status: show active subagent details in status output. - Diffs: split the default language pack and expand default Diffs language coverage while keeping the host floor aligned. ([#​87370](openclaw/openclaw#87370), [#​87372](openclaw/openclaw#87372)) Thanks [@​RomneyDa](https://github.com/RomneyDa). - ClawHub: add plugin display names plus skill verification and trust surfaces. ([#​87354](openclaw/openclaw#87354), [#​86699](openclaw/openclaw#86699)) Thanks [@​thewilloftheshadow](https://github.com/thewilloftheshadow) and [@​Patrick-Erichsen](https://github.com/Patrick-Erichsen). - iOS: refresh the dev app with Pro Command, Chat, Agents, Settings, hosted push relay defaults, and realtime Talk playback wired to gateway sessions, diagnostics, chat, and realtime Talk. ([#​87367](openclaw/openclaw#87367), [#​88096](openclaw/openclaw#88096), [#​88105](openclaw/openclaw#88105)) Thanks [@​Solvely-Colin](https://github.com/Solvely-Colin) and [@​ngutman](https://github.com/ngutman). - Docs: clarify Codex computer-use setup, paste-token stdin auth setup, macOS gateway sleep troubleshooting, native Codex hook relay recovery, container model auth, install deployment cards, device-token admin gating, CLI setup flow compatibility, Notte cloud browser CDP setup, and backport targets. ([#​87313](openclaw/openclaw#87313), [#​63050](openclaw/openclaw#63050), [#​87685](openclaw/openclaw#87685)) Thanks [@​bdjben](https://github.com/bdjben), [@​liaoandi](https://github.com/liaoandi), and [@​thewilloftheshadow](https://github.com/thewilloftheshadow). - PDF/tools: use ClawPDF for PDF extraction, support encrypted PDF extraction, and surface MCP structured content in agent tool results. ([#​87670](openclaw/openclaw#87670), [#​87751](openclaw/openclaw#87751)) - Providers: add Claude Opus 4.8 support, Fal Krea image model schemas, NVIDIA featured model catalogs, MiniMax streaming music responses, and provider-backed voice model catalogs. ([#​87845](openclaw/openclaw#87845), [#​87890](openclaw/openclaw#87890), [#​80775](openclaw/openclaw#80775), [#​84764](openclaw/openclaw#84764), [#​87794](openclaw/openclaw#87794)) Thanks [@​eleqtrizit](https://github.com/eleqtrizit) and [@​vincentkoc](https://github.com/vincentkoc). - Codex/GitHub: add the GitHub Copilot agent runtime and the Codex Supervisor plugin package. - Plugins: externalize GitHub Copilot and Tokenjuice as official install-on-demand plugins with npm and ClawHub publish metadata. - Workboard: add agent coordination tools for tracking and handing off active agent work. - Discord: show commentary in progress drafts so live Discord runs expose useful in-progress context. ([#​85200](openclaw/openclaw#85200)) - Plugin SDK: add a reply payload sending hook for plugins that need to deliver channel-owned replies and flatten package types for SDK declarations. ([#​82823](openclaw/openclaw#82823), [#​87165](openclaw/openclaw#87165)) Thanks [@​piersonr](https://github.com/piersonr) and [@​RomneyDa](https://github.com/RomneyDa). - Policy: add policy comparison, ingress-channel conformance, and sandbox-posture conformance checks. ([#​85572](openclaw/openclaw#85572), [#​85744](openclaw/openclaw#85744), [#​86768](openclaw/openclaw#86768)) ##### Fixes - Agents: fall back to local config pruning when the optional `agents delete` Gateway probe cannot authenticate, so offline installs can still delete agents without removing shared workspaces. - Tighten phone-control mutation authorization \[AI]. ([#​87150](openclaw/openclaw#87150)) Thanks [@​pgondhi987](https://github.com/pgondhi987). - Clarify directive persistence authorization policy \[AI]. ([#​86369](openclaw/openclaw#86369)) Thanks [@​pgondhi987](https://github.com/pgondhi987). - Agents/Codex: keep spawned agent cwd/workspace state separated, forward ACP spawn attachments, keep hook context prompt-local, release session locks on timeout abort and runtime teardown without deleting live OpenClaw-owned locks during cleanup, avoid session event queue self-wait, clean up exec abort listeners, stream assistant deltas incrementally, recover raw missing-thread compaction failures, preserve rotated compaction session identity, keep compaction-timeout snapshots continuable, preserve shared app-server state across startup or helper failures, keep native hook relay alive across restarts and prune stale bridge files, close native hook relay replacement races, keep Claude live tool progress visible for watchdog recovery, suppress abandoned requester completion handoff, route workspace memory through tools, resolve Codex runtime models first, report quarantined dynamic tools, format `skills` command output, bind node auto-review to prepared plans, retry Claude CLI transcript probes, and bound compaction/steering retries. ([#​87218](openclaw/openclaw#87218), [#​86875](openclaw/openclaw#86875), [#​86123](openclaw/openclaw#86123), [#​88129](openclaw/openclaw#88129), [#​87399](openclaw/openclaw#87399), [#​87375](openclaw/openclaw#87375), [#​72574](openclaw/openclaw#72574), [#​87383](openclaw/openclaw#87383), [#​87400](openclaw/openclaw#87400), [#​83022](openclaw/openclaw#83022), [#​87671](openclaw/openclaw#87671), [#​87738](openclaw/openclaw#87738), [#​87747](openclaw/openclaw#87747), [#​87706](openclaw/openclaw#87706), [#​87546](openclaw/openclaw#87546), [#​87541](openclaw/openclaw#87541), [#​81048](openclaw/openclaw#81048)) Thanks [@​mbelinky](https://github.com/mbelinky), [@​Alix-007](https://github.com/Alix-007), [@​luoyanglang](https://github.com/luoyanglang), [@​yetval](https://github.com/yetval), [@​sjf](https://github.com/sjf), [@​joshavant](https://github.com/joshavant), [@​benjamin1492](https://github.com/benjamin1492), [@​c19354837](https://github.com/c19354837), [@​fuller-stack-dev](https://github.com/fuller-stack-dev), [@​pfrederiksen](https://github.com/pfrederiksen), and [@​dodge1218](https://github.com/dodge1218). - Codex Supervisor: keep real-home app-server MCP session listing on the loaded state path, bound stored history scans, and close WebSocket probes cleanly. - Channels: thread canonical session keys into outbound hooks, preserve Matrix room-id case, keep fallback tool warnings mention-inert, retain delivered Slack final replies during late cleanup, continue iMessage polling after denied reactions, suppress duplicate native exec approvals, resolve Gateway message actions against the active runtime config, preserve Telegram SecretRef prompt config and polling keepalives, preserve WhatsApp profile auth roots, QR display, document filenames, and plugin hook config, suppress Discord recovered tool warnings, preserve the Discord voice outbound helper, cap Discord/Signal/Zalo channel request and container timeouts, and block untrusted Teams service URLs while keeping TeamsSDK patterns aligned. ([#​73706](openclaw/openclaw#73706), [#​75670](openclaw/openclaw#75670), [#​87366](openclaw/openclaw#87366), [#​87451](openclaw/openclaw#87451), [#​87465](openclaw/openclaw#87465), [#​87334](openclaw/openclaw#87334), [#​84535](openclaw/openclaw#84535), [#​76262](openclaw/openclaw#76262), [#​83304](openclaw/openclaw#83304), [#​82492](openclaw/openclaw#82492), [#​87581](openclaw/openclaw#87581), [#​77114](openclaw/openclaw#77114), [#​86426](openclaw/openclaw#86426), [#​85529](openclaw/openclaw#85529), [#​87160](openclaw/openclaw#87160)) Thanks [@​zeroaltitude](https://github.com/zeroaltitude), [@​lukeboyett](https://github.com/lukeboyett), [@​jarvis-mns1](https://github.com/jarvis-mns1), [@​xiaotian](https://github.com/xiaotian), [@​funmerlin](https://github.com/funmerlin), [@​joshavant](https://github.com/joshavant), [@​eleqtrizit](https://github.com/eleqtrizit), [@​heyitsaamir](https://github.com/heyitsaamir), [@​amittell](https://github.com/amittell), [@​lidge-jun](https://github.com/lidge-jun), [@​liorb-mountapps](https://github.com/liorb-mountapps), [@​masatohoshino](https://github.com/masatohoshino), [@​bladin](https://github.com/bladin), and [@​giodl73-repo](https://github.com/giodl73-repo). - CLI/auth/doctor/providers: reject malformed numeric/timeout/subcommand-version inputs, ignore workspace dotenv provider credentials, wait for respawn child shutdown, bound heartbeat defaults plus Codex, GitHub Copilot, OpenAI, Anthropic, Google, Feishu, LM Studio, MiniMax, Xiaomi TTS, and local-provider OAuth/token/model requests, harden Codex auth probes, label auth health by agent, preserve explicit agentRuntime pins during Codex model migration, warm provider auth off the main thread, honor Codex response timeouts, stop migrating current Claude Haiku 4.5 profiles to Sonnet, bound local service startup, resolve GPT-5.5 without cached catalog, migrate legacy memory auto-provider config, rewrite non-canonical `api_key` auth profiles, and make doctor restart follow-ups actionable. ([#​87398](openclaw/openclaw#87398), [#​86281](openclaw/openclaw#86281), [#​87361](openclaw/openclaw#87361), [#​88133](openclaw/openclaw#88133), [#​83655](openclaw/openclaw#83655), [#​87559](openclaw/openclaw#87559), [#​87719](openclaw/openclaw#87719), [#​88088](openclaw/openclaw#88088), [#​85924](openclaw/openclaw#85924), [#​84362](openclaw/openclaw#84362)) Thanks [@​Patrick-Erichsen](https://github.com/Patrick-Erichsen), [@​samzong](https://github.com/samzong), [@​giodl73-repo](https://github.com/giodl73-repo), [@​alkor2000](https://github.com/alkor2000), [@​mmaps](https://github.com/mmaps), [@​nxmxbbd](https://github.com/nxmxbbd), and [@​vincentkoc](https://github.com/vincentkoc). - Gateway/security/session state: expire browser tokens after auth rotation, scope assistant idempotency dedupe, drain probe client closes, avoid stale restart continuation reuse, preserve retry-after fallbacks and stale rate-limit cooldown probes, bound webchat image and artifact transcript scans, include seconds in inbound metadata timestamps, clear completed session active runs, clear stale chat stream buffers, and evict current plugin-state namespaces at row caps. ([#​87810](openclaw/openclaw#87810), [#​87833](openclaw/openclaw#87833), [#​75089](openclaw/openclaw#75089)) Thanks [@​joshavant](https://github.com/joshavant) and [@​litang9](https://github.com/litang9). - Config/parsing/network: reject partial numeric parsing, parse provider/Discord retry headers and dates strictly, honor IPv6 and bare IPv6 `no_proxy` entries, preserve empty plugin allowlists, canonicalize secret target array indexes, and reject malformed media content lengths, inspected TCP ports, marketplace content lengths, cron epochs, sandbox stat fields, unsafe duration values, empty config path segments, noncanonical schema array refs, unsafe Telegram callback pages, and invalid Teams attachment-fetch DNS targets. ([#​87883](openclaw/openclaw#87883)) Thanks [@​zhangguiping-xydt](https://github.com/zhangguiping-xydt). - Browser/input hardening: reject invalid tab indexes, excessive viewport resizes, explicit zero CDP ports, malformed geolocation options, unsafe screenshot or permission-grant timeouts, loose response-body limits, invalid cookie expiries, and non-finite Browser tool delays/timeouts. - Cron/automation: retry recurring jobs after transient model rate limits before waiting for the next scheduled slot, and preflight model fallbacks before skipping scheduled work. ([#​82887](openclaw/openclaw#82887)) Thanks [@​chen-zhang-cs-code](https://github.com/chen-zhang-cs-code). - Auto-reply/directives: respect provider and relayed channel metadata during directive persistence so channel-originated decisions keep their intended context. ([#​87683](openclaw/openclaw#87683)) - WhatsApp: resolve the auth directory from the active profile so profile-scoped WhatsApp installs do not drift to the wrong credential root. ([#​82492](openclaw/openclaw#82492)) Thanks [@​lidge-jun](https://github.com/lidge-jun). - Gateway/session state: clear completed session active runs, avoid cold-loading providers for MCP inventory, cache single-session child indexes, cap handshake timers, and bound preauth, auth-guard, media, transcript, readiness, and port options. - Channels/replies: preserve channel-owned progress callbacks when verbose output is off, keep group-room progress suppression intact, prefer external session delivery context, escape Discord component id delimiters, force final TUI chat repaints, show Slack reasoning previews, and normalize Discord/Matrix/Mattermost channel numeric options. ([#​87476](openclaw/openclaw#87476), [#​87423](openclaw/openclaw#87423)) - Agents/tool args: harden smart-quoted argument repair for edit arrays and exact escaped arguments so model-produced tool calls recover without corrupting valid input. ([#​86611](openclaw/openclaw#86611)) Thanks [@​ferminquant](https://github.com/ferminquant). - Providers/agents: preserve seeded Anthropic signatures, preserve signed thinking payloads, concatenate signature-delta chunks, preserve DeepSeek `reasoning_content` replay across tier suffixes, apply OpenRouter strict9 ids to Mistral routes, promote Ollama plain-text tool calls, load NVIDIA featured model catalogs, stream MiniMax music generation responses, and recover empty preflight compaction. ([#​87593](openclaw/openclaw#87593), [#​87493](openclaw/openclaw#87493), [#​80775](openclaw/openclaw#80775), [#​84764](openclaw/openclaw#84764)) Thanks [@​Pluviobyte](https://github.com/Pluviobyte) and [@​eleqtrizit](https://github.com/eleqtrizit). - Media/images: skip CLI image cache refs when resolving generated images, allow trusted generated HTML attachments, and bound generated video downloads so stale refs and slow providers fail cleanly. ([#​87523](openclaw/openclaw#87523), [#​87982](openclaw/openclaw#87982)) - File transfer: handle late tar stdin pipe errors after archive validation or unpacking has already settled. - Performance: trust install-record caches between reloads, prefer native JSON parsing, reuse unchanged tool-search catalogs, reuse gateway session and plugin metadata paths, skip unchanged store serialization, patch single-entry session writes, add precomputed session patch writers, reduce store clone allocations, cache manifest model catalog rows and auto-enabled plugin config, avoid full session snapshots for entry reads, defer configured Slack full startup, prefer bundled plugin dist entries, and slim current metadata identity caches. ([#​87760](openclaw/openclaw#87760)) - Docker/release/QA: package runtime workspace templates, stream cross-OS served artifacts, preserve sparse Crabbox run artifacts, isolate npm plugin installs per package, reject incompatible package plugin API installs, drop the leftover root Sharp dependency from package manifests after the Rastermill migration, bound OpenClaw instance logs, plugin gauntlet relay logs, MCP channel buffers, kitchen-sink scans, agent-turn assertions, QA-Lab credential broker calls, QA Matrix substrate requests, and release scenario logs, and keep release/google live guards current. ([#​87647](openclaw/openclaw#87647), [#​87477](openclaw/openclaw#87477)) Thanks [@​rohitjavvadi](https://github.com/rohitjavvadi) and [@​vincentkoc](https://github.com/vincentkoc). - Release/CI: bound manual git fetches, ClawHub verifier responses, ClawHub owner metadata, dependency-guard error bodies, Parallels limits, startup/test/memory budget parsing, and diffs viewer build warnings so release lanes fail with useful proof instead of hanging. ([#​87839](openclaw/openclaw#87839)) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL3BhdGNoIl19--> Reviewed-on: https://git.erwanleboucher.dev/eleboucher/homelab/pulls/759
* fix(telegram): enable TCP keepalive on getUpdates connections to prevent NAT timeout stalls Long-polling connections to api.telegram.org stay idle for up to the getUpdates timeout (~900 s). Most home/office NAT tables expire idle TCP entries after 60–1800 s (commonly ~1000 s). When the NAT entry is silently dropped the connection hangs rather than returning an error, leaving the grammY runner stuck until the 90 s stall watchdog fires and forces a restart cycle. Fix: unconditionally set `keepAlive: true` and `keepAliveInitialDelay: 30_000` (30 s) on the undici Agent `connect` options built in `buildTelegramConnectOptions`. OS-level TCP keepalive probes sent every ~75 s (OS default) will: 1. Refresh the NAT table entry before it expires. 2. Surface dead connections immediately with ETIMEDOUT instead of hanging forever. The `return Object.keys(connect).length > 0 ? connect : null` guard is also removed; `connect` is now always non-empty so it always returns the object. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> (cherry picked from commit 92e454c) * fix(telegram): stop self-flagging disconnected on poll-cycle start; widen channel connect grace to 300s (cherry picked from commit 1ca963a) * fix(telegram): catch hung polling startups that preserve inherited connected:true The widened 300s channel connect grace and the removal of connected:false from notePollingStart left a path where a polling restart could hang forever looking healthy. notePollingStart clears lastConnectedAt, lastEventAt, and lastTransportActivityAt but deliberately omits connected, so server-channels' patch-merge inherits a connected:true from the previous lifecycle. After grace, evaluateChannelHealth's stale-socket branch requires lastTransportActivityAt to be non-null and the connected:false branch is masked, so the channel sits healthy with no first getUpdates. Add a post-grace branch to evaluateChannelHealth that flags polling channels as stale-socket when connected:true is paired with null lastConnectedAt and null lastTransportActivityAt and a non-null lastStartAt. Scoped to mode:polling so webhook channels and channels without continuous transport tracking are not falsely flagged. Align TELEGRAM_POLLING_CONNECT_GRACE_MS in the Telegram status diagnostic with DEFAULT_CHANNEL_CONNECT_GRACE_MS so openclaw channels status agrees with the shared health monitor on the grace window. Refresh the notePollingStart comment to point at the new evaluateChannelHealth branch. Addresses clawsweeper review on openclaw#83304 (P1 connect-grace startup-hang, P2 diagnostic grace drift). Tests cover the new flagged path, the in-grace happy path, and the prior-successful-connect happy path. * fix(telegram): clear polling connected state on startup * fix(gateway): add defense-in-depth health-policy branch for hung polling startups Defense in depth on top of 87db46c's notePollingStart connected:false fix. The primary path (notePollingStart writes connected:false explicitly so evaluateChannelHealth's existing connected===false branch catches a hung restart) is unchanged. This adds a defensive post-grace branch that catches the same hang via a different signature -- inherited connected:true paired with null lastConnectedAt and null lastTransportActivityAt -- in case a future code path forgets to clear the inherited connected flag on lifecycle start. Scoped to mode:polling so webhook channels and channels without continuous transport tracking are not falsely flagged. Also bump lastStartAt: Date.now() - 121_000 to 301_000 in the spool-handler timeout test added by upstream openclaw#83505 so it falls past the widened 300s TELEGRAM_POLLING_CONNECT_GRACE_MS suppression window (mirroring the same fixup already applied to the two adjacent polling-startup tests). * revert(telegram,gateway): keep connect grace at 120s Drop the 120s -> 300s widening from this PR after maintainer feedback that the extra grace masks real startup bugs. The defense-in-depth checks added in earlier commits (notePollingStart clearing inherited connected state, the stale-socket policy branch, the per-snapshot startup grace test) all work fine at 120s and remain valuable on their own. Reverts in: - src/gateway/channel-health-policy.ts: DEFAULT_CHANNEL_CONNECT_GRACE_MS 300 -> 120 - extensions/telegram/src/status-issues.ts: TELEGRAM_POLLING_CONNECT_GRACE_MS 300 -> 120 - extensions/telegram/src/status.test.ts: lastStartAt 301_000 -> 121_000 (3 cases) The new channel-health-policy.test.ts cases use explicit channelConnectGraceMs: 10_000 in the policy, so they are unaffected by the default constant change. * fix(telegram): narrow polling keepalive fix --------- Co-authored-by: Yibei Ou <yibeiou@Yibeis-Mac-mini.local> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(telegram): enable TCP keepalive on getUpdates connections to prevent NAT timeout stalls Long-polling connections to api.telegram.org stay idle for up to the getUpdates timeout (~900 s). Most home/office NAT tables expire idle TCP entries after 60–1800 s (commonly ~1000 s). When the NAT entry is silently dropped the connection hangs rather than returning an error, leaving the grammY runner stuck until the 90 s stall watchdog fires and forces a restart cycle. Fix: unconditionally set `keepAlive: true` and `keepAliveInitialDelay: 30_000` (30 s) on the undici Agent `connect` options built in `buildTelegramConnectOptions`. OS-level TCP keepalive probes sent every ~75 s (OS default) will: 1. Refresh the NAT table entry before it expires. 2. Surface dead connections immediately with ETIMEDOUT instead of hanging forever. The `return Object.keys(connect).length > 0 ? connect : null` guard is also removed; `connect` is now always non-empty so it always returns the object. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> (cherry picked from commit 92e454c) * fix(telegram): stop self-flagging disconnected on poll-cycle start; widen channel connect grace to 300s (cherry picked from commit 1ca963a) * fix(telegram): catch hung polling startups that preserve inherited connected:true The widened 300s channel connect grace and the removal of connected:false from notePollingStart left a path where a polling restart could hang forever looking healthy. notePollingStart clears lastConnectedAt, lastEventAt, and lastTransportActivityAt but deliberately omits connected, so server-channels' patch-merge inherits a connected:true from the previous lifecycle. After grace, evaluateChannelHealth's stale-socket branch requires lastTransportActivityAt to be non-null and the connected:false branch is masked, so the channel sits healthy with no first getUpdates. Add a post-grace branch to evaluateChannelHealth that flags polling channels as stale-socket when connected:true is paired with null lastConnectedAt and null lastTransportActivityAt and a non-null lastStartAt. Scoped to mode:polling so webhook channels and channels without continuous transport tracking are not falsely flagged. Align TELEGRAM_POLLING_CONNECT_GRACE_MS in the Telegram status diagnostic with DEFAULT_CHANNEL_CONNECT_GRACE_MS so openclaw channels status agrees with the shared health monitor on the grace window. Refresh the notePollingStart comment to point at the new evaluateChannelHealth branch. Addresses clawsweeper review on openclaw#83304 (P1 connect-grace startup-hang, P2 diagnostic grace drift). Tests cover the new flagged path, the in-grace happy path, and the prior-successful-connect happy path. * fix(telegram): clear polling connected state on startup * fix(gateway): add defense-in-depth health-policy branch for hung polling startups Defense in depth on top of 87db46c's notePollingStart connected:false fix. The primary path (notePollingStart writes connected:false explicitly so evaluateChannelHealth's existing connected===false branch catches a hung restart) is unchanged. This adds a defensive post-grace branch that catches the same hang via a different signature -- inherited connected:true paired with null lastConnectedAt and null lastTransportActivityAt -- in case a future code path forgets to clear the inherited connected flag on lifecycle start. Scoped to mode:polling so webhook channels and channels without continuous transport tracking are not falsely flagged. Also bump lastStartAt: Date.now() - 121_000 to 301_000 in the spool-handler timeout test added by upstream openclaw#83505 so it falls past the widened 300s TELEGRAM_POLLING_CONNECT_GRACE_MS suppression window (mirroring the same fixup already applied to the two adjacent polling-startup tests). * revert(telegram,gateway): keep connect grace at 120s Drop the 120s -> 300s widening from this PR after maintainer feedback that the extra grace masks real startup bugs. The defense-in-depth checks added in earlier commits (notePollingStart clearing inherited connected state, the stale-socket policy branch, the per-snapshot startup grace test) all work fine at 120s and remain valuable on their own. Reverts in: - src/gateway/channel-health-policy.ts: DEFAULT_CHANNEL_CONNECT_GRACE_MS 300 -> 120 - extensions/telegram/src/status-issues.ts: TELEGRAM_POLLING_CONNECT_GRACE_MS 300 -> 120 - extensions/telegram/src/status.test.ts: lastStartAt 301_000 -> 121_000 (3 cases) The new channel-health-policy.test.ts cases use explicit channelConnectGraceMs: 10_000 in the policy, so they are unaffected by the default constant change. * fix(telegram): narrow polling keepalive fix --------- Co-authored-by: Yibei Ou <yibeiou@Yibeis-Mac-mini.local> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Summary
Lower Telegram polling's TCP keepalive initial delay from undici's default 60s to 30s.
Telegram
getUpdateslong-poll requests can sit idle for a long time. On some home or office networks, NAT entries expire around the same time as undici's default first keepalive probe, leaving no safety margin before the connection goes stale. This patch makes the Telegram dispatcher pass explicit keepalive options for direct, env-proxy, and explicit-proxy transports.The PR was narrowed after review:
Test plan
node scripts/run-vitest.mjs extensions/telegram/src/fetch.test.tsgit diff --checkReal behavior proof
mac-mini.lanwith two Telegram polling accounts andrh-bot.lanwith one Telegram polling account. This narrowed final head was also verified locally with focused dispatcher-policy tests.openclaw gateway statusandopenclaw channels status; final narrowed PR head usednode scripts/run-vitest.mjs extensions/telegram/src/fetch.test.tsandgit diff --check.fetch.test.tsnow asserts that direct and env-proxy Telegram dispatchers carrykeepAlive: trueandkeepAliveInitialDelay: 30_000in the connect options.running, connectedduring the soak with runtime uptime measured in days rather than repeated restart cycles. On the narrowed final head, 37 Telegram fetch tests passed locally and the PR diff is narrowed toextensions/telegram/src/fetch.tsandextensions/telegram/src/fetch.test.ts.