Skip to content

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

Open
BingqingLyu wants to merge 1 commit into
mainfrom
fork-pr-60137-fix-slack-transient-network-empty-message
Open

fix(infra): handle Slack SDK empty-message wrapper and undefined rejection reasons#1905
BingqingLyu wants to merge 1 commit into
mainfrom
fork-pr-60137-fix-slack-transient-network-empty-message

Conversation

@BingqingLyu

@BingqingLyu BingqingLyu commented Apr 28, 2026

Copy link
Copy Markdown
Owner

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 openclaw#24582 and openclaw#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 openclaw#23169 (closed as stale, not fixed — 4 users reported the same crash)
Fixes openclaw#21082 (closed as stale, not fixed — 3 additional reproductions in comments)
Relates to openclaw#43689 (undefined rejection variant, still open)

…ction reasons

The @slack/web-api SDK wraps network errors via requestErrorWithOriginal()
under the code slack_webapi_request_error. When the original error has an
empty message (observed during macOS sleep/wake and prolonged network outages),
the resulting wrapper message is just 'A request error occurred: ' — which
matches no existing transient snippet or code, causing process.exit(1) and
gateway crash loops.

Additionally, @slack/socket-mode can call reject() without an argument during
WebSocket TLS errors, producing an undefined rejection reason that falls through
all classification checks to process.exit(1).

Changes:
1. Recognize slack_webapi_request_error code + request wrapper message pattern
   as inherently transient, even when the inner error has no identifiable code
2. Treat undefined/null rejection reasons as non-fatal with a warning log

Fixes openclaw#23169, openclaw#21082
Relates to openclaw#43689
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants