openclaw - ✅(Solved) Fix [slack] Unhandled promise rejection after WebSocket 408 on reconnect [1 pull requests, 1 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#43689Fetched 2026-04-08 00:17:11
View on GitHub
Comments
0
Participants
1
Timeline
4
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×2referenced ×1subscribed ×1

Error Message

{"0":"[WARN] socket-mode:SlackWebSocket:53 A pong wasn't received from the server before the timeout of 5000ms!","_meta":{"date":"2026-03-11T21:48:32.309Z","logLevelName":"WARN","path":{"fileNameWithLine":"subsystem-kzdGVyce.js:945","method":"console.warn"}}}

{"0":"[ERROR] socket-mode:SlackWebSocket:58 WebSocket error occurred: Unexpected server response: 408","_meta":{"date":"2026-03-11T21:49:23.801Z","logLevelName":"ERROR","path":{"fileNameWithLine":"subsystem-kzdGVyce.js:946","method":"console.error"}}}

{"0":"[ERROR] socket-mode:SocketModeClient:52 WebSocket error! Error: Unexpected server response: 408","_meta":{"date":"2026-03-11T21:49:23.803Z","logLevelName":"ERROR","path":{"fileNameWithLine":"subsystem-kzdGVyce.js:946","method":"console.error"}}}

{"0":"{"subsystem":"gateway/channels/slack"}","1":"slack socket disconnected (error). retry 1/12 in 2s (Unexpected server response: 408)","_meta":{"date":"2026-03-11T21:49:23.818Z","logLevelName":"ERROR","path":{"fileNameWithLine":"subsystem-kzdGVyce.js:1112"}}}

{"0":"[openclaw] Unhandled promise rejection: undefined","_meta":{"date":"2026-03-11T21:49:23.825Z","logLevelName":"ERROR","path":{"fileNameWithLine":"subsystem-kzdGVyce.js:946","method":"console.error"}}}

Root Cause

Root cause hypothesis

Fix Action

Fixed

PR fix notes

PR #60137: fix(infra): handle Slack SDK empty-message wrapper and undefined rejection reasons

Description (problem / solution / changelog)

Problem

The @slack/web-api SDK wraps all network errors via requestErrorWithOriginal() under the code slack_webapi_request_error. The existing transient detection (added in #24582 and #23787) handles the common case where the wrapped error message contains a recognizable network code (e.g., ENOTFOUND, ENETUNREACH).

However, two edge cases still crash the gateway:

1. Empty original message (crash loop on network outage)

During macOS sleep/wake or prolonged connectivity drops, the Slack SDK wraps a network error whose .message is empty, producing:

A request error occurred:

This matches no existing transient snippet or code → process.exit(1) → launchd restart → crash loop.

Observed in production: Apr 2, 2026 — 160+ restarts in 20 minutes on a macOS host, causing ephemeral port exhaustion and total network death.

2. undefined rejection reason (WebSocket TLS errors)

@slack/socket-mode can call reject() without an argument during WebSocket TLS disconnect errors. The resulting undefined reason falls through all classification checks (AbortError, fatal, config, transient) to process.exit(1).

Fix

Two complementary changes:

  1. isTransientNetworkError(): Recognize slack_webapi_request_error code + request wrapper message pattern (/^A request error occurred:/i or empty) as inherently transient, even when the inner error has no identifiable network code. The guard ensures genuine API errors (e.g., invalid_auth) are NOT suppressed.

  2. installUnhandledRejectionHandler(): Treat undefined/null rejection reasons as non-fatal with a distinct warning log ("Non-fatal unhandled rejection (undefined reason, continuing)"), instead of crashing.

Test coverage

  • Slack wrapper with empty original message → transient ✅
  • Slack wrapper with no message at all → transient ✅
  • Slack wrapper with recognizable network code → transient ✅ (existing, still passes)
  • Slack error with non-wrapper message (invalid_auth) → still fatal ✅
  • undefined rejection → non-fatal ✅
  • null rejection → non-fatal ✅
  • String rejection → still fatal ✅

All 55 tests pass.

Related issues

Fixes #23169 (closed as stale, not fixed — 4 users reported the same crash) Fixes #21082 (closed as stale, not fixed — 3 additional reproductions in comments) Relates to #43689 (undefined rejection variant, still open)

Changed files

  • src/infra/unhandled-rejections.fatal-detection.test.ts (modified, +43/-7)
  • src/infra/unhandled-rejections.test.ts (modified, +39/-2)
  • src/infra/unhandled-rejections.ts (modified, +50/-0)

Code Example

{"0":"[WARN]  socket-mode:SlackWebSocket:53 A pong wasn't received from the server before the timeout of 5000ms!","_meta":{"date":"2026-03-11T21:48:32.309Z","logLevelName":"WARN","path":{"fileNameWithLine":"subsystem-kzdGVyce.js:945","method":"console.warn"}}}

{"0":"[ERROR]  socket-mode:SlackWebSocket:58 WebSocket error occurred: Unexpected server response: 408","_meta":{"date":"2026-03-11T21:49:23.801Z","logLevelName":"ERROR","path":{"fileNameWithLine":"subsystem-kzdGVyce.js:946","method":"console.error"}}}

{"0":"[ERROR]  socket-mode:SocketModeClient:52 WebSocket error! Error: Unexpected server response: 408","_meta":{"date":"2026-03-11T21:49:23.803Z","logLevelName":"ERROR","path":{"fileNameWithLine":"subsystem-kzdGVyce.js:946","method":"console.error"}}}

{"0":"{\"subsystem\":\"gateway/channels/slack\"}","1":"slack socket disconnected (error). retry 1/12 in 2s (Unexpected server response: 408)","_meta":{"date":"2026-03-11T21:49:23.818Z","logLevelName":"ERROR","path":{"fileNameWithLine":"subsystem-kzdGVyce.js:1112"}}}

{"0":"[openclaw] Unhandled promise rejection: undefined","_meta":{"date":"2026-03-11T21:49:23.825Z","logLevelName":"ERROR","path":{"fileNameWithLine":"subsystem-kzdGVyce.js:946","method":"console.error"}}}
RAW_BUFFERClick to expand / collapse

Environment

  • OpenClaw version: 2026.3.8 (3caab92)
  • Platform: Linux x64 / Node 22.22.0
  • Channel: Slack (Socket Mode)

Summary

After a health-monitor-triggered socket restart, the Slack WebSocket connection experienced a pong timeout followed by a HTTP 408 error, which triggered an Unhandled promise rejection: undefined in the gateway process.

Timeline (all UTC, 2026-03-11)

TimeEvent
21:25:36health-monitor: restarting (reason: stale-socket)
21:25:36slack socket mode connected — reconnect successful
21:48:32WARN: A pong wasn't received from the server before the timeout of 5000ms
21:49:23ERROR: WebSocket error occurred: Unexpected server response: 408
21:49:23ERROR: WebSocket error! Error: Unexpected server response: 408
21:49:23slack socket disconnected (error). retry 1/12 in 2s
21:49:23[openclaw] Unhandled promise rejection: undefined

Relevant log entries (structured, PII redacted)

{"0":"[WARN]  socket-mode:SlackWebSocket:53 A pong wasn't received from the server before the timeout of 5000ms!","_meta":{"date":"2026-03-11T21:48:32.309Z","logLevelName":"WARN","path":{"fileNameWithLine":"subsystem-kzdGVyce.js:945","method":"console.warn"}}}

{"0":"[ERROR]  socket-mode:SlackWebSocket:58 WebSocket error occurred: Unexpected server response: 408","_meta":{"date":"2026-03-11T21:49:23.801Z","logLevelName":"ERROR","path":{"fileNameWithLine":"subsystem-kzdGVyce.js:946","method":"console.error"}}}

{"0":"[ERROR]  socket-mode:SocketModeClient:52 WebSocket error! Error: Unexpected server response: 408","_meta":{"date":"2026-03-11T21:49:23.803Z","logLevelName":"ERROR","path":{"fileNameWithLine":"subsystem-kzdGVyce.js:946","method":"console.error"}}}

{"0":"{\"subsystem\":\"gateway/channels/slack\"}","1":"slack socket disconnected (error). retry 1/12 in 2s (Unexpected server response: 408)","_meta":{"date":"2026-03-11T21:49:23.818Z","logLevelName":"ERROR","path":{"fileNameWithLine":"subsystem-kzdGVyce.js:1112"}}}

{"0":"[openclaw] Unhandled promise rejection: undefined","_meta":{"date":"2026-03-11T21:49:23.825Z","logLevelName":"ERROR","path":{"fileNameWithLine":"subsystem-kzdGVyce.js:946","method":"console.error"}}}

Root cause hypothesis

The Unhandled promise rejection: undefined fires from subsystem-kzdGVyce.js:946 — the same line as the console.error WebSocket error handler. The rejection value is undefined, suggesting the error handler itself is either throwing or returning a rejected promise without a value. This is likely a missing .catch() on a promise inside the WebSocket error callback.

The process did not crash in this instance (the retry loop continued), but an unhandled rejection under different timing or Node.js --unhandled-rejections=throw mode could terminate the process.

Secondary request

When the socket recovers after a gap (health-monitor restart or retry success), it would be helpful to emit a brief user-visible notification so the operator knows there was a period of disconnection. Currently there is no signal to the user that the gateway was offline.

Steps to reproduce

Difficult to reproduce on demand — requires Slack's server returning a 408 after a pong timeout. The health-monitor stale-socket restart appears to be a prerequisite; the second disconnect occurred ~23 minutes after the first reconnect.

extent analysis

Fix Plan

To address the Unhandled promise rejection: undefined issue, we need to ensure that all promises within the WebSocket error callback are properly handled.

  1. Identify and modify the error handler: Locate the error handler in subsystem-kzdGVyce.js at line 946 and ensure it does not throw or return a rejected promise without a value.
  2. Add a .catch() block: Wrap any promises within the error handler with a .catch() block to handle any potential errors.

Example code:

// Before
websocket.onError((error) => {
  console.error('WebSocket error occurred:', error);
  // Potential promise without .catch()
  somePromiseThatMightReject();
});

// After
websocket.onError((error) => {
  console.error('WebSocket error occurred:', error);
  somePromiseThatMightReject().catch((error) => {
    console.error('Error in somePromiseThatMightReject:', error);
  });
});

Verification

To verify the fix, simulate a WebSocket error (e.g., by triggering a pong timeout) and check the logs for any Unhandled promise rejection errors. If the fix is successful, these errors should no longer appear.

Extra Tips

  • Consider adding a retry mechanism with exponential backoff for WebSocket connections to handle temporary disconnections.
  • Implement a user-visible notification when the socket recovers after a disconnection to inform the operator of the gateway's status.
  • Review the code for any other potential promise rejections and ensure they are properly handled.

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…

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 [slack] Unhandled promise rejection after WebSocket 408 on reconnect [1 pull requests, 1 participants]