Files
openclaw/src/slack/monitor/provider.auth-errors.test.ts
scoootscooob 1ae82be55a fix(slack): fail fast on non-recoverable auth errors instead of retry loop
When a Slack bot is removed from a workspace while still configured in
OpenClaw, the gateway enters an infinite retry loop on account_inactive
or invalid_auth errors, making the entire gateway unresponsive.

Add isNonRecoverableSlackAuthError() to detect permanent credential
failures (account_inactive, invalid_auth, token_revoked, etc.) and
throw immediately instead of retrying.  This mirrors how the Telegram
provider already distinguishes recoverable network errors from fatal
auth errors via isRecoverableTelegramNetworkError().

The check is applied in both the startup catch block and the disconnect
reconnect path so stale credentials always fail fast with a clear error
message.

Closes #32366

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 01:59:47 +00:00

52 lines
1.9 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { isNonRecoverableSlackAuthError } from "./provider.js";
describe("isNonRecoverableSlackAuthError", () => {
it.each([
"An API error occurred: account_inactive",
"An API error occurred: invalid_auth",
"An API error occurred: token_revoked",
"An API error occurred: token_expired",
"An API error occurred: not_authed",
"An API error occurred: org_login_required",
"An API error occurred: team_access_not_granted",
"An API error occurred: missing_scope",
"An API error occurred: cannot_find_service",
"An API error occurred: invalid_token",
])("returns true for non-recoverable error: %s", (msg) => {
expect(isNonRecoverableSlackAuthError(new Error(msg))).toBe(true);
});
it("returns true when error is a plain string", () => {
expect(isNonRecoverableSlackAuthError("account_inactive")).toBe(true);
});
it("matches case-insensitively", () => {
expect(isNonRecoverableSlackAuthError(new Error("ACCOUNT_INACTIVE"))).toBe(true);
expect(isNonRecoverableSlackAuthError(new Error("Invalid_Auth"))).toBe(true);
});
it.each([
"Connection timed out",
"ECONNRESET",
"Network request failed",
"socket hang up",
"ETIMEDOUT",
"rate_limited",
])("returns false for recoverable/transient error: %s", (msg) => {
expect(isNonRecoverableSlackAuthError(new Error(msg))).toBe(false);
});
it("returns false for non-error values", () => {
expect(isNonRecoverableSlackAuthError(null)).toBe(false);
expect(isNonRecoverableSlackAuthError(undefined)).toBe(false);
expect(isNonRecoverableSlackAuthError(42)).toBe(false);
expect(isNonRecoverableSlackAuthError({})).toBe(false);
});
it("returns false for empty string", () => {
expect(isNonRecoverableSlackAuthError("")).toBe(false);
expect(isNonRecoverableSlackAuthError(new Error(""))).toBe(false);
});
});