fix: clarify slack socket retry errors

This commit is contained in:
Peter Steinberger
2026-05-04 23:04:34 +01:00
parent d82992f0ae
commit 9f2c8a6ab6
4 changed files with 50 additions and 6 deletions

View File

@@ -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.

View File

@@ -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<string, Set<(...args: unknown[]) => 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<string, unknown> = {};
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 } };

View File

@@ -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);

View File

@@ -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";
}