diff --git a/CHANGELOG.md b/CHANGELOG.md index 91343558ceb..2f69caec1f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ Docs: https://docs.openclaw.ai - active-memory: skip the memory sub-agent gracefully instead of logging a confusing allowlist error when no memory plugin (`memory-core` or `memory-lancedb`) is loaded, so active-memory with no memory backend no longer produces misleading "No callable tools remain" warnings in the gateway log. Fixes #77506. Thanks @hclsys. - Memory/wiki: preserve representation from both corpora in `corpus=all` searches while backfilling unused result capacity, so memory hits are not starved by numerically higher wiki integer scores. Fixes #77337. Thanks @hclsys. - Telegram: clean up tool-only draft previews after assistant message boundaries so transient `Surfacing...` tool-status bubbles do not linger when no matching final preview arrives. Thanks @BunsDev. +- Slack: report `unknown error` instead of `undefined` in socket-mode startup retry logs and label the retry reason explicitly. - Telegram: let explicit forum-topic `requireMention` settings override persisted `/activate` and `/deactivate` state, so per-topic mention gates work consistently. Fixes #49864. Thanks @Panniantong. - Cron: surface failed isolated-run diagnostics in `cron show`, status, and run history when requested tools are unavailable, so blocked cron runs report the actual tool-policy failure instead of a misleading green result. Fixes #75763. Thanks @RyanSandoval. - TUI/escape abort: track the in-flight runId after `chat.send` resolves so pressing Esc during the gap before the first gateway event aborts the run instead of repeatedly printing `no active run`. Fixes #1296. Thanks @Lukavyi and @romneyda. diff --git a/extensions/slack/src/monitor/provider.reconnect.test.ts b/extensions/slack/src/monitor/provider.reconnect.test.ts index 3cdaaefc65c..2ca36bb952e 100644 --- a/extensions/slack/src/monitor/provider.reconnect.test.ts +++ b/extensions/slack/src/monitor/provider.reconnect.test.ts @@ -5,8 +5,11 @@ import { publishSlackDisconnectedStatus, startSlackSocketAndWaitForDisconnect, } from "./provider-support.js"; -import { formatSlackSocketReconnectMessage } from "./provider.js"; -import { waitForSlackSocketDisconnect } from "./reconnect-policy.js"; +import { + formatSlackSocketReconnectMessage, + formatSlackSocketStartRetryMessage, +} from "./provider.js"; +import { formatUnknownError, waitForSlackSocketDisconnect } from "./reconnect-policy.js"; class FakeEmitter { private listeners = new Map void>>(); @@ -97,6 +100,28 @@ describe("slack socket reconnect helpers", () => { ).toBe("slack socket disconnected (disconnect); reconnecting in 2s (attempt 1/12)"); }); + it("formats missing and unserializable socket errors without leaking undefined", () => { + const circular: Record = {}; + circular.self = circular; + + expect(formatUnknownError(undefined)).toBe("unknown error"); + expect(formatUnknownError(null)).toBe("unknown error"); + expect(formatUnknownError("")).toBe("unknown error"); + expect(formatUnknownError(new Error(""))).toBe("Error"); + expect(formatUnknownError(circular)).toBe("unknown error"); + }); + + it("formats socket start retries with an explicit reason field", () => { + expect( + formatSlackSocketStartRetryMessage({ + attempt: 1, + maxAttempts: 12, + delayMs: 2_340, + error: undefined, + }), + ).toBe('slack socket mode failed to start; retry 1/12 in 2s reason="unknown error"'); + }); + it("resolves disconnect waiter on socket disconnect event", async () => { const client = new FakeEmitter(); const app = { receiver: { client } }; diff --git a/extensions/slack/src/monitor/provider.ts b/extensions/slack/src/monitor/provider.ts index 46fb13459a5..986368d367f 100644 --- a/extensions/slack/src/monitor/provider.ts +++ b/extensions/slack/src/monitor/provider.ts @@ -97,6 +97,16 @@ export function formatSlackSocketReconnectMessage(params: { return `slack socket disconnected (${params.event}); reconnecting in ${Math.round(params.delayMs / 1000)}s (attempt ${params.attempt}/${maxAttempts})${suffix}`; } +export function formatSlackSocketStartRetryMessage(params: { + attempt: number; + maxAttempts: number; + delayMs: number; + error: unknown; +}) { + const maxAttempts = params.maxAttempts > 0 ? String(params.maxAttempts) : "∞"; + return `slack socket mode failed to start; retry ${params.attempt}/${maxAttempts} in ${Math.round(params.delayMs / 1000)}s reason="${formatUnknownError(params.error)}"`; +} + function parseApiAppIdFromAppToken(raw?: string) { const token = raw?.trim(); if (!token) { @@ -534,7 +544,12 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) { } const delayMs = computeBackoff(SLACK_SOCKET_RECONNECT_POLICY, reconnectAttempts); runtime.error?.( - `slack socket mode failed to start. retry ${reconnectAttempts}/${SLACK_SOCKET_RECONNECT_POLICY.maxAttempts || "∞"} in ${Math.round(delayMs / 1000)}s (${formatUnknownError(err)})`, + formatSlackSocketStartRetryMessage({ + attempt: reconnectAttempts, + maxAttempts: SLACK_SOCKET_RECONNECT_POLICY.maxAttempts, + delayMs, + error: err, + }), ); try { await sleepWithAbort(delayMs, opts.abortSignal); diff --git a/extensions/slack/src/monitor/reconnect-policy.ts b/extensions/slack/src/monitor/reconnect-policy.ts index 2c1d7bde9d9..319fd561b47 100644 --- a/extensions/slack/src/monitor/reconnect-policy.ts +++ b/extensions/slack/src/monitor/reconnect-policy.ts @@ -94,14 +94,17 @@ export function isNonRecoverableSlackAuthError(error: unknown): boolean { } export function formatUnknownError(error: unknown): string { + if (error === undefined || error === null) { + return "unknown error"; + } if (error instanceof Error) { - return error.message; + return error.message || error.name || "unknown error"; } if (typeof error === "string") { - return error; + return error || "unknown error"; } try { - return JSON.stringify(error); + return JSON.stringify(error) ?? "unknown error"; } catch { return "unknown error"; }