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>
52 lines
1.9 KiB
TypeScript
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);
|
|
});
|
|
});
|