fix: reduce slack socket reconnect log noise

This commit is contained in:
Peter Steinberger
2026-05-03 18:33:06 +01:00
parent 11a08e3ef0
commit aa5f8e6403
5 changed files with 108 additions and 5 deletions

View File

@@ -39,6 +39,7 @@ Docs: https://docs.openclaw.ai
- CLI/doctor: trust a ready gateway memory probe when CLI-side active memory backend resolution is unavailable, preventing false "No active memory plugin is registered" warnings for healthy runtime setups. Fixes #76792. Thanks @som-686.
- Memory/status: keep plain `openclaw memory status` and `openclaw memory status --json` on the cheap read-only path by reserving vector and embedding provider probes for `--deep` or `--index`. Fixes #76769. Thanks @daruire.
- Telegram: suppress stale same-session replies when a newer accepted message arrives before an older in-flight Telegram dispatch finalizes. Fixes #76642. Thanks @chinar-amrutkar.
- Slack: collapse routine Socket Mode pong-timeout reconnects into one OpenClaw reconnect line and suppress the duplicate Slack SDK pong warning.
- Gateway/diagnostics: abort-drain embedded runs after an extended no-progress stall so a single dead session no longer leaves queued Discord/channel turns blocked behind repeated `recovery=none` liveness warnings.
- Plugins/ClawHub: accept the live artifact resolver `kind`/`sha256` field names alongside the typed `artifactKind`/`artifactSha256` form so `clawhub:` installs of npm-pack and legacy ZIP packages no longer miss downloadable artifacts. Thanks @romneyda.
- Control UI/Sessions: avoid full `sessions.list` reloads for chat-turn `sessions.changed` payloads, so large session stores no longer add multi-second delays while chat responses are being delivered. (#76676) Thanks @VACInc.

View File

@@ -10,8 +10,13 @@ type SlackSocketModeConfig = Pick<
SlackSocketModeReceiverOptions,
"clientPingTimeout" | "serverPingTimeout" | "pingPongLoggingEnabled"
>;
type SlackSdkLogger = NonNullable<SlackSocketModeReceiverOptions["logger"]>;
type SlackSdkLogLevel = ReturnType<SlackSdkLogger["getLevel"]>;
const OPENCLAW_SLACK_CLIENT_PING_TIMEOUT_MS = 15_000;
const SLACK_SOCKET_PONG_TIMEOUT_WARNING_PREFIX = "A pong wasn't received from the server";
const SLACK_SOCKET_LOG_LEVEL_IGNORED_WARNING_RE =
/^The logLevel given to .+ was ignored as you also gave logger$/;
export type SlackBoltResolvedExports = {
App: SlackAppConstructor;
@@ -133,6 +138,42 @@ export function publishSlackDisconnectedStatus(
});
}
function isSlackSocketPongTimeoutWarning(args: readonly unknown[]) {
return (
typeof args[0] === "string" && args[0].startsWith(SLACK_SOCKET_PONG_TIMEOUT_WARNING_PREFIX)
);
}
function isSlackSocketSelfInflictedLoggerWarning(args: readonly unknown[]) {
return typeof args[0] === "string" && SLACK_SOCKET_LOG_LEVEL_IGNORED_WARNING_RE.test(args[0]);
}
export function createSlackSocketModeLogger(
sink: Pick<typeof console, "debug" | "info" | "warn" | "error"> = console,
): SlackSdkLogger {
let level = "info" as SlackSdkLogLevel;
let name = "socket-mode";
const prefix = () => `socket-mode:${name}`;
return {
debug: () => {},
info: () => {},
warn: (...args: unknown[]) => {
if (isSlackSocketPongTimeoutWarning(args) || isSlackSocketSelfInflictedLoggerWarning(args)) {
return;
}
sink.warn(prefix(), ...args);
},
error: (...args: unknown[]) => sink.error(prefix(), ...args),
setLevel: (nextLevel) => {
level = nextLevel;
},
getLevel: () => level,
setName: (nextName) => {
name = nextName;
},
};
}
function asRecord(value: unknown): Record<string, unknown> | undefined {
return value && typeof value === "object" && !Array.isArray(value)
? (value as Record<string, unknown>)
@@ -181,6 +222,7 @@ export function createSlackBoltApp(params: {
autoReconnectEnabled: false,
clientPingTimeout:
params.socketMode?.clientPingTimeout ?? OPENCLAW_SLACK_CLIENT_PING_TIMEOUT_MS,
logger: createSlackSocketModeLogger(),
installerOptions: {
clientOptions: params.clientOptions,
},

View File

@@ -1,6 +1,7 @@
import { describe, expect, it } from "vitest";
import {
createSlackBoltApp,
createSlackSocketModeLogger,
resolveSlackBoltInterop,
shouldSkipOpenClawSlackSelfEvent,
} from "./provider-support.js";
@@ -159,6 +160,10 @@ describe("createSlackBoltApp", () => {
appToken: "xapp-test",
autoReconnectEnabled: false,
clientPingTimeout: 15_000,
logger: expect.objectContaining({
error: expect.any(Function),
warn: expect.any(Function),
}),
installerOptions: {
clientOptions,
},
@@ -200,6 +205,10 @@ describe("createSlackBoltApp", () => {
clientPingTimeout: 20_000,
serverPingTimeout: 45_000,
pingPongLoggingEnabled: true,
logger: expect.objectContaining({
error: expect.any(Function),
warn: expect.any(Function),
}),
installerOptions: {
clientOptions,
},
@@ -264,6 +273,23 @@ describe("createSlackBoltApp", () => {
expect(eagerAuthTestCalls).toBe(0);
});
it("suppresses Slack's redundant pong timeout warning while forwarding other SDK warnings", () => {
const warnCalls: unknown[][] = [];
const logger = createSlackSocketModeLogger({
debug: () => {},
info: () => {},
warn: (...args: unknown[]) => warnCalls.push(args),
error: () => {},
});
logger.setName("SlackWebSocket:1");
logger.warn("A pong wasn't received from the server before the timeout of 15000ms!");
logger.warn("The logLevel given to Socket Mode was ignored as you also gave logger");
logger.warn("another socket warning");
expect(warnCalls).toEqual([["socket-mode:SlackWebSocket:1", "another socket warning"]]);
});
it("keeps Bolt self filtering except assistant message_changed events", () => {
expect(
shouldSkipOpenClawSlackSelfEvent({

View File

@@ -5,6 +5,7 @@ import {
publishSlackDisconnectedStatus,
startSlackSocketAndWaitForDisconnect,
} from "./provider-support.js";
import { formatSlackSocketReconnectMessage } from "./provider.js";
import { waitForSlackSocketDisconnect } from "./reconnect-policy.js";
class FakeEmitter {
@@ -85,6 +86,17 @@ describe("slack socket reconnect helpers", () => {
});
});
it("formats recoverable disconnects as a single reconnect status line", () => {
expect(
formatSlackSocketReconnectMessage({
event: "disconnect",
attempt: 1,
maxAttempts: 12,
delayMs: 2_340,
}),
).toBe("slack socket disconnected (disconnect); reconnecting in 2s (attempt 1/12)");
});
it("resolves disconnect waiter on socket disconnect event", async () => {
const client = new FakeEmitter();
const app = { receiver: { client } };

View File

@@ -85,6 +85,18 @@ async function getSlackBoltInterop(): Promise<SlackBoltResolvedExports> {
const SLACK_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
const SLACK_WEBHOOK_BODY_TIMEOUT_MS = 30_000;
export function formatSlackSocketReconnectMessage(params: {
event: string;
attempt: number;
maxAttempts: number;
delayMs: number;
error?: unknown;
}) {
const maxAttempts = params.maxAttempts > 0 ? String(params.maxAttempts) : "∞";
const suffix = params.error ? ` (${formatUnknownError(params.error)})` : "";
return `slack socket disconnected (${params.event}); reconnecting in ${Math.round(params.delayMs / 1000)}s (attempt ${params.attempt}/${maxAttempts})${suffix}`;
}
function parseApiAppIdFromAppToken(raw?: string) {
const token = raw?.trim();
if (!token) {
@@ -443,6 +455,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
try {
if (slackMode === "socket") {
let reconnectAttempts = 0;
let hasLoggedSocketConnected = false;
while (!opts.abortSignal?.aborted) {
try {
const disconnect = await startSlackSocketAndWaitForDisconnect({
@@ -451,7 +464,10 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
onStarted: () => {
reconnectAttempts = 0;
publishSlackConnectedStatus(opts.setStatus);
runtime.log?.("slack socket mode connected");
if (!hasLoggedSocketConnected) {
hasLoggedSocketConnected = true;
runtime.log?.("slack socket mode connected");
}
},
});
if (!disconnect) {
@@ -483,10 +499,16 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
}
const delayMs = computeBackoff(SLACK_SOCKET_RECONNECT_POLICY, reconnectAttempts);
runtime.error?.(
`slack socket disconnected (${disconnect.event}). retry ${reconnectAttempts}/${SLACK_SOCKET_RECONNECT_POLICY.maxAttempts || "∞"} in ${Math.round(delayMs / 1000)}s${
disconnect.error ? ` (${formatUnknownError(disconnect.error)})` : ""
}`,
runtime.log?.(
warn(
formatSlackSocketReconnectMessage({
event: disconnect.event,
attempt: reconnectAttempts,
maxAttempts: SLACK_SOCKET_RECONNECT_POLICY.maxAttempts,
delayMs,
error: disconnect.error,
}),
),
);
await gracefulStop();
try {