fix(slack): remove message.channels/message.groups handlers that crash Bolt 4.6 (#32033)

* fix(slack): remove message.channels/message.groups handlers that crash Bolt 4.6

Bolt 4.6 rejects app.event() calls with event names starting with
"message." (e.g. "message.channels", "message.groups"), throwing
AppInitializationError on startup. These handlers were added in #31701
based on the incorrect assumption that Slack dispatches typed event
names to Bolt. In reality, Slack always delivers events with
type:"message" regardless of the Event Subscription name; the
channel_type field distinguishes the source.

The generic app.event("message") handler already receives all channel,
group, IM, and MPIM messages. The additional typed handlers were
unreachable even if Bolt allowed them, since no event payload ever
carries type:"message.channels".

This preserves the handleIncomingMessageEvent refactor from #31701
(extracting the handler into a named function) while removing only
the broken registrations.

Fixes the Slack provider crash loop affecting all accounts on
@slack/bolt >= 4.6.0.

Closes #31674 (original issue was not caused by missing handlers)

* fix: document Slack Bolt 4.6 startup handler fix (#32033) (thanks @mahopan)

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Maho
2026-03-02 13:32:42 -05:00
committed by GitHub
parent 738f5d4533
commit d21cf44452
3 changed files with 19 additions and 26 deletions

View File

@@ -47,6 +47,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Slack/Bolt startup compatibility: remove invalid `message.channels` and `message.groups` event registrations so Slack providers no longer crash on startup with Bolt 4.6+; channel/group traffic continues through the unified `message` handler (`channel_type`). (#32033) Thanks @mahopan.
- Sandbox/Docker setup command parsing: accept `agents.*.sandbox.docker.setupCommand` as either a string or a string array, and normalize arrays to newline-delimited shell scripts so multi-step setup commands no longer concatenate without separators. (#31953) Thanks @liuxiaopai-ai.
- Gateway/Plugin HTTP route precedence: run explicit plugin HTTP routes before the Control UI SPA catch-all so registered plugin webhook/custom paths remain reachable, while unmatched paths still fall through to Control UI handling. (#31885) Thanks @Sid-Qin.
- Security/Node exec approvals: preserve shell/dispatch-wrapper argv semantics during approval hardening so approved wrapper commands (for example `env sh -c ...`) cannot drift into a different runtime command shape, and add regression coverage for both approval-plan generation and approved runtime execution paths. Thanks @tdjackey for reporting.

View File

@@ -33,8 +33,6 @@ function createMessageHandlers(overrides?: SlackSystemEventTestOverrides) {
});
return {
handler: harness.getHandler("message") as MessageHandler | null,
channelHandler: harness.getHandler("message.channels") as MessageHandler | null,
groupHandler: harness.getHandler("message.groups") as MessageHandler | null,
handleSlackMessage,
};
}
@@ -159,17 +157,17 @@ describe("registerSlackMessageEvents", () => {
expect(messageQueueMock).not.toHaveBeenCalled();
});
it("registers and forwards message.channels and message.groups events", async () => {
it("handles channel and group messages via the unified message handler", async () => {
messageQueueMock.mockClear();
messageAllowMock.mockReset().mockResolvedValue([]);
const { channelHandler, groupHandler, handleSlackMessage } = createMessageHandlers({
const { handler, handleSlackMessage } = createMessageHandlers({
dmPolicy: "open",
channelType: "channel",
});
expect(channelHandler).toBeTruthy();
expect(groupHandler).toBeTruthy();
expect(handler).toBeTruthy();
// channel_type distinguishes the source; all arrive as event type "message"
const channelMessage = {
type: "message",
channel: "C1",
@@ -178,8 +176,8 @@ describe("registerSlackMessageEvents", () => {
text: "hello channel",
ts: "123.100",
};
await channelHandler!({ event: channelMessage, body: {} });
await groupHandler!({
await handler!({ event: channelMessage, body: {} });
await handler!({
event: {
...channelMessage,
channel_type: "group",
@@ -193,17 +191,19 @@ describe("registerSlackMessageEvents", () => {
expect(messageQueueMock).not.toHaveBeenCalled();
});
it("applies subtype system-event handling for message.channels events", async () => {
it("applies subtype system-event handling for channel messages", async () => {
messageQueueMock.mockClear();
messageAllowMock.mockReset().mockResolvedValue([]);
const { channelHandler, handleSlackMessage } = createMessageHandlers({
const { handler, handleSlackMessage } = createMessageHandlers({
dmPolicy: "open",
channelType: "channel",
});
expect(channelHandler).toBeTruthy();
expect(handler).toBeTruthy();
await channelHandler!({
// message_changed events from channels arrive via the generic "message"
// handler with channel_type:"channel" — not a separate event type.
await handler!({
event: {
...makeChangedEvent({ channel: "C1", user: "U1" }),
channel_type: "channel",

View File

@@ -46,23 +46,15 @@ export function registerSlackMessageEvents(params: {
}
};
// NOTE: Slack Event Subscriptions use names like "message.channels" and
// "message.groups" to control *which* message events are delivered, but the
// actual event payload always arrives with `type: "message"`. The
// `channel_type` field ("channel" | "group" | "im" | "mpim") distinguishes
// the source. Bolt rejects `app.event("message.channels")` since v4.6
// because it is a subscription label, not a valid event type.
ctx.app.event("message", async ({ event, body }: SlackEventMiddlewareArgs<"message">) => {
await handleIncomingMessageEvent({ event, body });
});
// Slack may dispatch channel/group message subscriptions under typed event
// names. Register explicit handlers so both delivery styles are supported.
ctx.app.event(
"message.channels",
async ({ event, body }: SlackEventMiddlewareArgs<"message.channels">) => {
await handleIncomingMessageEvent({ event, body });
},
);
ctx.app.event(
"message.groups",
async ({ event, body }: SlackEventMiddlewareArgs<"message.groups">) => {
await handleIncomingMessageEvent({ event, body });
},
);
ctx.app.event("app_mention", async ({ event, body }: SlackEventMiddlewareArgs<"app_mention">) => {
try {