mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
fix(slack): log typing reaction errors instead of silently swallowing them (#103303)
* fix(slack): log typing reaction errors instead of silently swallowing them The typing reaction (emoji) add and remove calls both use empty catch handlers, silently discarding all errors. If the bot token lacks reactions:write scope, the operator has no way to diagnose why the configured typing reaction never appears. Replace the empty catch with logVerbose calls that record the Slack error, matching the existing logging pattern in the same file. * test(slack): cover typing reaction failures --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -28,6 +28,7 @@ const stopSlackStreamMock = vi.fn(async (_params?: unknown) => ({}) as { message
|
||||
const emitSlackMessageSentHooksMock = vi.fn(() => {});
|
||||
const reactSlackMessageMock = vi.fn(async () => {});
|
||||
const removeSlackReactionMock = vi.fn(async () => {});
|
||||
const logVerboseMock = vi.fn();
|
||||
class TestSlackStreamNotDeliveredError extends Error {
|
||||
readonly pendingText: string;
|
||||
readonly slackCode: string;
|
||||
@@ -200,6 +201,14 @@ function requireCapturedTyping() {
|
||||
return capturedTyping;
|
||||
}
|
||||
|
||||
function createSlackPlatformError(error: string, details?: { needed?: string; provided?: string }) {
|
||||
// Mirrors @slack/web-api 7.18.0 platformErrorFromResult: message plus structured result data.
|
||||
return Object.assign(new Error(`An API error occurred: ${error}`), {
|
||||
code: "slack_webapi_platform_error",
|
||||
data: { ok: false, error, ...details },
|
||||
});
|
||||
}
|
||||
|
||||
function requireCapturedItemEventHandler() {
|
||||
const handler = capturedReplyOptions?.onItemEvent;
|
||||
if (!handler) {
|
||||
@@ -819,7 +828,7 @@ vi.mock("openclaw/plugin-sdk/reply-payload", () => ({
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/runtime-env", () => ({
|
||||
danger: (message: string) => message,
|
||||
logVerbose: () => {},
|
||||
logVerbose: logVerboseMock,
|
||||
shouldLogVerbose: () => false,
|
||||
}));
|
||||
|
||||
@@ -1258,6 +1267,7 @@ describe("dispatchPreparedSlackMessage preview fallback", () => {
|
||||
stopSlackStreamMock.mockReset();
|
||||
reactSlackMessageMock.mockReset();
|
||||
removeSlackReactionMock.mockReset();
|
||||
logVerboseMock.mockReset();
|
||||
for (const value of Object.values(statusReactionControllerMock)) {
|
||||
value.mockClear();
|
||||
}
|
||||
@@ -1982,6 +1992,39 @@ describe("dispatchPreparedSlackMessage preview fallback", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("logs the formatted Slack error when adding the typing reaction fails", async () => {
|
||||
reactSlackMessageMock.mockRejectedValueOnce(
|
||||
createSlackPlatformError("missing_scope", {
|
||||
needed: "reactions:write",
|
||||
provided: "chat:write",
|
||||
}),
|
||||
);
|
||||
|
||||
await dispatchPreparedSlackMessage(
|
||||
createPreparedSlackMessage({ typingReaction: "hourglass_flowing_sand" }),
|
||||
);
|
||||
await expect(requireCapturedTyping().start()).resolves.toBeUndefined();
|
||||
|
||||
expect(logVerboseMock).toHaveBeenCalledWith(
|
||||
"slack send: typing reaction failed: An API error occurred: missing_scope; code: slack_webapi_platform_error; slack error: missing_scope; needed: reactions:write; provided: chat:write",
|
||||
);
|
||||
});
|
||||
|
||||
it("logs the formatted Slack error when removing the typing reaction fails", async () => {
|
||||
removeSlackReactionMock.mockRejectedValueOnce(createSlackPlatformError("invalid_auth"));
|
||||
|
||||
await dispatchPreparedSlackMessage(
|
||||
createPreparedSlackMessage({ typingReaction: "hourglass_flowing_sand" }),
|
||||
);
|
||||
const typing = requireCapturedTyping();
|
||||
await typing.start();
|
||||
await expect(typing.stop?.()).resolves.toBeUndefined();
|
||||
|
||||
expect(logVerboseMock).toHaveBeenCalledWith(
|
||||
"slack send: typing reaction removal failed: An API error occurred: invalid_auth; code: slack_webapi_platform_error; slack error: invalid_auth",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps Slack status reactions when channel replies are message-tool-only", async () => {
|
||||
await dispatchPreparedSlackMessage(
|
||||
createPreparedSlackMessage({
|
||||
|
||||
@@ -614,7 +614,9 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
|
||||
await reactSlackMessage(message.channel, message.ts, typingReaction, {
|
||||
token: ctx.botToken,
|
||||
client: slackClient,
|
||||
}).catch(() => {});
|
||||
}).catch((err: unknown) => {
|
||||
logVerbose(`slack send: typing reaction failed: ${formatSlackError(err)}`);
|
||||
});
|
||||
}
|
||||
},
|
||||
stop: async () => {
|
||||
@@ -632,7 +634,9 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
|
||||
await removeSlackReaction(message.channel, message.ts, typingReaction, {
|
||||
token: ctx.botToken,
|
||||
client: slackClient,
|
||||
}).catch(() => {});
|
||||
}).catch((err: unknown) => {
|
||||
logVerbose(`slack send: typing reaction removal failed: ${formatSlackError(err)}`);
|
||||
});
|
||||
}
|
||||
},
|
||||
onStartError: (err) => {
|
||||
|
||||
Reference in New Issue
Block a user