From b768d01a8525d990d2353d83b67a127568bf7974 Mon Sep 17 00:00:00 2001 From: Liu Minwen Date: Fri, 4 Sep 2026 18:02:38 +0800 Subject: [PATCH] fix(#1): catch Slack API rejections in turn-handler reactions.add / reactions.remove / chat.delete / chat.update at src/slack/turn-handler.ts were fired without a .catch handler. When Slack rejects (rate limit, 5xx, expired token), the derived promise becomes an unhandledRejection and Node terminates the process by default, dropping the bot offline. Wrap each call with .catch(() => undefined) so reaction/list-management failures stay isolated to the turn path and align with how deliveries.ts handles Slack API errors elsewhere in the same package. Refs: Jackallink/qm-integration#1 --- src/slack/turn-handler.ts | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/slack/turn-handler.ts b/src/slack/turn-handler.ts index 0214c17a9..e50de318d 100644 --- a/src/slack/turn-handler.ts +++ b/src/slack/turn-handler.ts @@ -266,9 +266,16 @@ export function createTurnHandler(deps: { const ts = await postReply(rendered); if (ts) await taskList?.attach(ts, rendered); }, - addReaction: (name) => client.reactions.add({ channel: inc.channel, timestamp: inc.ts, name }).then(() => {}), + addReaction: (name) => + client.reactions + .add({ channel: inc.channel, timestamp: inc.ts, name }) + .then(() => {}) + .catch(() => undefined), removeReaction: (name) => - client.reactions.remove({ channel: inc.channel, timestamp: inc.ts, name }).then(() => {}), + client.reactions + .remove({ channel: inc.channel, timestamp: inc.ts, name }) + .then(() => {}) + .catch(() => undefined), emojiCandidates: [...DEFAULT_ACK_REACTIONS], emojiPick: ackEmoji.requestAckEmoji(text, ackEmoji.ackPickCandidates(client), { channel: inc.channel, @@ -279,13 +286,20 @@ export function createTurnHandler(deps: { taskList = createTaskListPresenter({ post: (text, blocks) => postReply(text, blocks), update: (ts, text, blocks) => - client.chat.update({ channel: inc.channel, ts, text, blocks, ...botIdentityArgs() }).then(() => { - mirrorSelfPost(inc.channel, ts, text, { sub: replyThreadTs, editedAt: Date.now() }); - }), + client.chat + .update({ channel: inc.channel, ts, text, blocks, ...botIdentityArgs() }) + .then(() => { + mirrorSelfPost(inc.channel, ts, text, { sub: replyThreadTs, editedAt: Date.now() }); + }) + .catch(() => undefined), checkpoint: async (ts) => { if (queuedRunId) await checkpointRunEditRef(queuedRunId, ts); }, - remove: (ts) => client.chat.delete({ channel: inc.channel, ts }).then(() => {}), + remove: (ts) => + client.chat + .delete({ channel: inc.channel, ts }) + .then(() => {}) + .catch(() => undefined), onSurfacePosted: () => ack?.onSurfacePosted(), onError: (error) => console.error("[slack-plugin] task-list update failed:", (error as Error).message), });