openclaw - ✅(Solved) Fix Gateway crashes on transient ENETUNREACH — uncaughtException handler lacks isTransientNetworkError check [2 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

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#60515Fetched 2026-04-08 02:50:06
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×1referenced ×1

The gateway's uncaughtException handler unconditionally calls process.exit(1) on transient network errors like ENETUNREACH, even though the unhandledRejection handler already has isTransientNetworkError() logic that correctly identifies and suppresses these.

When a socket-level ENETUNREACH surfaces through the synchronous callback chain in undici's Client.connect → Node's net.connect, it fires as an uncaughtException rather than an unhandledRejection, bypassing the transient-error suppression.

Error Message

[telegram] fetch fallback: enabling sticky IPv4-only dispatcher (codes=ENETUNREACH) [telegram] fetch fallback: DNS-resolved IP unreachable; trying alternative Telegram API IP (codes=ENETUNREACH) [openclaw] Uncaught exception: Error: connect ENETUNREACH 149.154.167.220:443 - Local (0.0.0.0:0) at internalConnect (node:net:1110:16) ... at file:///.../dist/ssrf-BWlfjI7J.js:152:3 ... at Client.connect (.../undici/lib/core/connect.js:70:20)

Root Cause

index.js:46-49 — the uncaughtException handler:

process.on("uncaughtException", (error) => {
    console.error("[openclaw] Uncaught exception:", formatUncaughtError(error));
    process.exit(1);  // unconditional exit
});

unhandled-rejections-BWJ-75jM.js — the unhandledRejection handler already has:

function isTransientNetworkError(err) { /* checks ENETUNREACH, ECONNRESET, ETIMEDOUT, etc. */ }

The isTransientNetworkError() function is exported from the unhandled-rejections module but is never called in the uncaughtException path.

Fix Action

Fix / Workaround

[telegram] fetch fallback: enabling sticky IPv4-only dispatcher (codes=ENETUNREACH)
[telegram] fetch fallback: DNS-resolved IP unreachable; trying alternative Telegram API IP (codes=ENETUNREACH)
[openclaw] Uncaught exception: Error: connect ENETUNREACH 149.154.167.220:443 - Local (0.0.0.0:0)
    at internalConnect (node:net:1110:16)
    ...
    at file:///.../dist/ssrf-BWlfjI7J.js:152:3
    ...
    at Client.connect (.../undici/lib/core/connect.js:70:20)

PR fix notes

PR #60627: fix: suppress transient network errors in uncaughtException handler

Description (problem / solution / changelog)

Summary

The gateway's uncaughtException handler unconditionally calls process.exit(1), causing the process to crash on transient ENETUNREACH errors that surface through undici's synchronous callback chain (e.g., Telegram fetch fallback). The sibling unhandledRejection handler already suppresses these via isTransientNetworkError(), but the uncaughtException path was missing the same check.

This PR wires up isTransientNetworkError and isAbortError checks in both uncaughtException handlers:

  • src/index.ts:94 — main module entry (static import)
  • src/cli/run-main.ts:182 — CLI runner entry (dynamic import, matching existing pattern)

Transient errors are now logged as warnings and the process continues, matching the existing unhandledRejection behavior. Non-transient exceptions still exit.

Root cause

Introduced in 183601c (fix: honor line default setup status) by @Takhoffman — the uncaughtException handlers were added without the transient-error guard that installUnhandledRejectionHandler() already provides.

Completeness (G8)

Grepped entire codebase for uncaughtException — only 2 sites exist (src/index.ts, src/cli/run-main.ts). Both are fixed.

Verification (G9)

Call path: undici sync callback → Node uncaughtException event → handler checks isTransientNetworkError(error) → returns early with console.warn instead of process.exit(1).

isTransientNetworkError already covers ENETUNREACH (in TRANSIENT_NETWORK_CODES set, line 33 of src/infra/unhandled-rejections.ts) and is exercised by existing tests in src/infra/unhandled-rejections.test.ts.

Test plan

  • Existing isTransientNetworkError unit tests cover ENETUNREACH and all transient codes
  • Lint passes (oxlint: 0 warnings, 0 errors on both files)
  • Manual: trigger ENETUNREACH via brief network outage with Telegram channel configured — gateway should log warning and continue

Closes #60515

Changed files

  • src/cli/run-main.ts (modified, +29/-6)
  • src/index.ts (modified, +17/-1)

PR #63780: fix(telegram): suppress recoverable uncaught network failures

Description (problem / solution / changelog)

Summary

  • add uncaught-exception handler registration alongside unhandled rejection handlers
  • suppress recoverable transient network uncaught exceptions in the top-level entrypoints
  • let the Telegram monitor reuse its polling restart path when recoverable network failures surface as uncaught exceptions
  • add regression coverage for the Telegram restart path and CLI uncaught ENETUNREACH suppression

Fixes #60515.

Validation

  • node scripts/run-vitest.mjs run --config vitest.extension-telegram.config.ts extensions/telegram/src/monitor.test.ts
  • node scripts/run-vitest.mjs run --config vitest.cli.config.ts src/cli/run-main.exit.test.ts
  • NODE_OPTIONS=--max-old-space-size=8192 pnpm exec tsc -p tsconfig.json --noEmit --pretty false --incremental false

Changed files

  • extensions/telegram/src/monitor.test.ts (modified, +86/-19)
  • extensions/telegram/src/monitor.ts (modified, +21/-11)
  • src/cli/run-main.exit.test.ts (modified, +41/-0)
  • src/cli/run-main.ts (modified, +28/-6)
  • src/index.ts (modified, +20/-1)
  • src/infra/unhandled-rejections.ts (modified, +25/-0)
  • src/plugin-sdk/runtime-env.ts (modified, +4/-1)
  • src/plugin-sdk/runtime.ts (modified, +4/-1)

Code Example

[telegram] fetch fallback: enabling sticky IPv4-only dispatcher (codes=ENETUNREACH)
[telegram] fetch fallback: DNS-resolved IP unreachable; trying alternative Telegram API IP (codes=ENETUNREACH)
[openclaw] Uncaught exception: Error: connect ENETUNREACH 149.154.167.220:443 - Local (0.0.0.0:0)
    at internalConnect (node:net:1110:16)
    ...
    at file:///.../dist/ssrf-BWlfjI7J.js:152:3
    ...
    at Client.connect (.../undici/lib/core/connect.js:70:20)

---

process.on("uncaughtException", (error) => {
    console.error("[openclaw] Uncaught exception:", formatUncaughtError(error));
    process.exit(1);  // unconditional exit
});

---

function isTransientNetworkError(err) { /* checks ENETUNREACH, ECONNRESET, ETIMEDOUT, etc. */ }
RAW_BUFFERClick to expand / collapse

Summary

The gateway's uncaughtException handler unconditionally calls process.exit(1) on transient network errors like ENETUNREACH, even though the unhandledRejection handler already has isTransientNetworkError() logic that correctly identifies and suppresses these.

When a socket-level ENETUNREACH surfaces through the synchronous callback chain in undici's Client.connect → Node's net.connect, it fires as an uncaughtException rather than an unhandledRejection, bypassing the transient-error suppression.

Steps to reproduce

  1. Run the gateway with a Telegram channel configured
  2. Cause a brief network outage (or have a transient IPv6 routing failure)
  3. The Telegram fetch fallback fires, tries alternative IPs, but if those also fail:
[telegram] fetch fallback: enabling sticky IPv4-only dispatcher (codes=ENETUNREACH)
[telegram] fetch fallback: DNS-resolved IP unreachable; trying alternative Telegram API IP (codes=ENETUNREACH)
[openclaw] Uncaught exception: Error: connect ENETUNREACH 149.154.167.220:443 - Local (0.0.0.0:0)
    at internalConnect (node:net:1110:16)
    ...
    at file:///.../dist/ssrf-BWlfjI7J.js:152:3
    ...
    at Client.connect (.../undici/lib/core/connect.js:70:20)

The gateway exits with status 1.

Root cause

index.js:46-49 — the uncaughtException handler:

process.on("uncaughtException", (error) => {
    console.error("[openclaw] Uncaught exception:", formatUncaughtError(error));
    process.exit(1);  // unconditional exit
});

unhandled-rejections-BWJ-75jM.js — the unhandledRejection handler already has:

function isTransientNetworkError(err) { /* checks ENETUNREACH, ECONNRESET, ETIMEDOUT, etc. */ }

The isTransientNetworkError() function is exported from the unhandled-rejections module but is never called in the uncaughtException path.

Expected behavior

The uncaughtException handler should call isTransientNetworkError(error) before deciding to exit. If the error is transient, log it and continue (same as the unhandledRejection handler does).

Environment

  • openclaw v2026.4.2
  • Node.js v22.22.0
  • Linux (Fedora 43)
  • Telegram channel with dual-stack (IPv4+IPv6) networking

extent analysis

TL;DR

The uncaughtException handler should be modified to call isTransientNetworkError(error) before exiting the process to handle transient network errors like ENETUNREACH.

Guidance

  • Modify the uncaughtException handler in index.js to call isTransientNetworkError(error) and only exit the process if the error is not transient.
  • Import the isTransientNetworkError function from the unhandled-rejections module in index.js to use it in the uncaughtException handler.
  • Log the transient network error and continue running the process if isTransientNetworkError(error) returns true.
  • Test the modified uncaughtException handler with transient network errors like ENETUNREACH to ensure it behaves as expected.

Example

const { isTransientNetworkError } = require('./unhandled-rejections-BWJ-75jM.js');

process.on("uncaughtException", (error) => {
    console.error("[openclaw] Uncaught exception:", formatUncaughtError(error));
    if (!isTransientNetworkError(error)) {
        process.exit(1);
    }
});

Notes

This solution assumes that the isTransientNetworkError function correctly identifies transient network errors. If this function is not accurate, the uncaughtException handler may not behave as expected.

Recommendation

Apply the workaround by modifying the uncaughtException handler to call isTransientNetworkError(error) before exiting the process. This will allow the gateway to continue running and handle transient network errors like ENETUNREACH correctly.

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

The uncaughtException handler should call isTransientNetworkError(error) before deciding to exit. If the error is transient, log it and continue (same as the unhandledRejection handler does).

Still need to ship something?

×6

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

Back to top recommendations

TRENDING