Handle transient Slack request errors without crashing the gateway (openclaw#23787) thanks @graysurf

Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: graysurf <10785178+graysurf@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
graysurf
2026-03-02 00:42:42 +08:00
committed by GitHub
parent bd78a74298
commit a54b85822c
3 changed files with 25 additions and 0 deletions

View File

@@ -94,6 +94,7 @@ Docs: https://docs.openclaw.ai
- Cron/Schedule errors: notify users when a job is auto-disabled after repeated schedule computation failures. (#29098) Thanks . - Cron/Schedule errors: notify users when a job is auto-disabled after repeated schedule computation failures. (#29098) Thanks .
- File tools/tilde paths: expand `~/...` against the user home directory before workspace-root checks in host file read/write/edit paths, while preserving root-boundary enforcement so outside-root targets remain blocked. (#29779) Thanks @Glucksberg. - File tools/tilde paths: expand `~/...` against the user home directory before workspace-root checks in host file read/write/edit paths, while preserving root-boundary enforcement so outside-root targets remain blocked. (#29779) Thanks @Glucksberg.
- Slack/HTTP mode startup: treat Slack HTTP accounts as configured when `botToken` + `signingSecret` are present (without requiring `appToken`) in channel config/runtime status so webhook mode is not silently skipped. (#30567) - Slack/HTTP mode startup: treat Slack HTTP accounts as configured when `botToken` + `signingSecret` are present (without requiring `appToken`) in channel config/runtime status so webhook mode is not silently skipped. (#30567)
- Slack/Transient request errors: classify Slack request-error messages like `Client network socket disconnected before secure TLS connection was established` as transient in unhandled-rejection fatal detection, preventing temporary network drops from crash-looping the gateway. (#23169)
- Slack/Usage footer formatting: wrap session keys in inline code in full response-usage footers so Slack does not parse colon-delimited session segments as emoji shortcodes. (#30258) Thanks @pushkarsingh32. - Slack/Usage footer formatting: wrap session keys in inline code in full response-usage footers so Slack does not parse colon-delimited session segments as emoji shortcodes. (#30258) Thanks @pushkarsingh32.
- Slack/Socket Mode slash startup: treat `app.options()` registration as best-effort and fall back to static arg menus when listener registration fails, preventing Slack monitor startup crash loops on receiver init edge cases. (#21715) - Slack/Socket Mode slash startup: treat `app.options()` registration as best-effort and fall back to static arg menus when listener registration fails, preventing Slack monitor startup crash loops on receiver init edge cases. (#21715)
- Slack/Legacy streaming config: map boolean `channels.slack.streaming=false` to unified streaming mode `off` (with `nativeStreaming=false`) so legacy configs correctly disable draft preview/native streaming instead of defaulting to `partial`. (#25990) Thanks @chilu18. - Slack/Legacy streaming config: map boolean `channels.slack.streaming=false` to unified streaming mode `off` (with `nativeStreaming=false`) so legacy configs correctly disable draft preview/native streaming instead of defaulting to `partial`. (#25990) Thanks @chilu18.

View File

@@ -93,10 +93,22 @@ describe("installUnhandledRejectionHandler - fatal detection", () => {
Object.assign(new Error("DNS resolve failed"), { code: "UND_ERR_DNS_RESOLVE_FAILED" }), Object.assign(new Error("DNS resolve failed"), { code: "UND_ERR_DNS_RESOLVE_FAILED" }),
Object.assign(new Error("Connection reset"), { code: "ECONNRESET" }), Object.assign(new Error("Connection reset"), { code: "ECONNRESET" }),
Object.assign(new Error("Timeout"), { code: "ETIMEDOUT" }), Object.assign(new Error("Timeout"), { code: "ETIMEDOUT" }),
Object.assign(
new Error(
"A request error occurred: Client network socket disconnected before secure TLS connection was established",
),
{ code: "slack_webapi_request_error" },
),
Object.assign(new Error("A request error occurred: getaddrinfo EAI_AGAIN slack.com"), { Object.assign(new Error("A request error occurred: getaddrinfo EAI_AGAIN slack.com"), {
code: "slack_webapi_request_error", code: "slack_webapi_request_error",
original: { code: "EAI_AGAIN", syscall: "getaddrinfo", hostname: "slack.com" }, original: { code: "EAI_AGAIN", syscall: "getaddrinfo", hostname: "slack.com" },
}), }),
Object.assign(new Error("A request error occurred: unknown"), {
code: "slack_webapi_request_error",
original: Object.assign(new Error("connect timeout"), {
code: "UND_ERR_CONNECT_TIMEOUT",
}),
}),
]; ];
for (const transientErr of transientCases) { for (const transientErr of transientCases) {
@@ -119,6 +131,17 @@ describe("installUnhandledRejectionHandler - fatal detection", () => {
); );
}); });
it("exits on non-transient Slack request errors", () => {
const slackErr = Object.assign(
new Error("A request error occurred: invalid request payload"),
{
code: "slack_webapi_request_error",
},
);
expectExitCodeFromUnhandled(slackErr, [1]);
});
it("does not exit on AbortError and logs suppression warning", () => { it("does not exit on AbortError and logs suppression warning", () => {
const abortErr = new Error("This operation was aborted"); const abortErr = new Error("This operation was aborted");
abortErr.name = "AbortError"; abortErr.name = "AbortError";

View File

@@ -49,6 +49,7 @@ const TRANSIENT_NETWORK_MESSAGE_CODE_RE =
const TRANSIENT_NETWORK_MESSAGE_SNIPPETS = [ const TRANSIENT_NETWORK_MESSAGE_SNIPPETS = [
"getaddrinfo", "getaddrinfo",
"socket hang up", "socket hang up",
"client network socket disconnected before secure tls connection was established",
"network error", "network error",
"network is unreachable", "network is unreachable",
"temporary failure in name resolution", "temporary failure in name resolution",