Skip to content

Add Slack Socket Mode support#162

Merged
dancer merged 25 commits intomainfrom
123-slack-socket-mode-support
Apr 20, 2026
Merged

Add Slack Socket Mode support#162
dancer merged 25 commits intomainfrom
123-slack-socket-mode-support

Conversation

@haydenbleasel
Copy link
Copy Markdown
Contributor

@haydenbleasel haydenbleasel commented Mar 2, 2026

Summary

  • Adds serverless-compatible Socket Mode forwarding to the Slack adapter, mirroring the Discord gateway pattern
  • A cron-invoked listener maintains the WebSocket, acks events, and forwards them via HTTP POST to the existing webhook endpoint using an x-slack-socket-token header
  • The adapter stays in mode: "webhook" (default) — socket mode listener is a separate external concern
  • routeSocketEvent() now accepts WebhookOptions and uses waitUntil for async handlers
  • New startSocketModeListener() / runSocketModeListener() / forwardSocketEvent() methods
  • Example cron route at /api/slack/socket-mode with createPersistentListener for Redis-based cross-instance coordination
  • Cron runs every 9 min, listener duration 10 min (same as Discord)

Test plan

  • Forwarded event accepted with valid appToken → 200
  • Forwarded event rejected with invalid token → 401
  • Forwarded event rejected when no appToken configured → 401
  • Bypasses signature verification for forwarded events
  • Options passthrough to handlers for forwarded events
  • startSocketModeListener returns 200 with valid config
  • startSocketModeListener returns 500 without waitUntil or appToken
  • routeSocketEvent passes options through to handlers
  • All 258 tests pass
  • Full pnpm validate passes (knip, check, typecheck, test, build)

Closes #123

@haydenbleasel haydenbleasel linked an issue Mar 2, 2026 that may be closed by this pull request
1 task
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Mar 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chat Ready Ready Preview, Comment, Open in v0 Apr 20, 2026 3:08pm
chat-sdk-nextjs-chat Ready Ready Preview, Comment, Open in v0 Apr 20, 2026 3:08pm

@socket-security
Copy link
Copy Markdown

socket-security bot commented Mar 2, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​slack/​socket-mode@​2.0.69910010089100

View full report

Comment thread packages/adapter-slack/src/index.ts Outdated
haydenbleasel and others added 14 commits March 16, 2026 13:20
…nteractive payloads can cause unhandled promise rejections that crash the Node.js process.

This commit fixes the issue reported at packages/adapter-slack/src/index.ts:1152

**Bug Analysis:**

In `routeSocketEvent` (line 1150), which is a synchronous `void` method, two async operations produce floating promises:

1.  `this.handleSlashCommand(params)` (line 1165) - `handleSlashCommand` is `async` and always returns a `Promise<Response>`. It calls `await this.lookupUser(userId)` which internally calls `await this.chat.getState().get()` (before the try/catch around the API call), and `this.chat.processSlashCommand()`. Any of these could throw.
    
2.  `this.dispatchInteractivePayload(payload)` (line 1172) - Returns `Response | Promise<Response>`. When the payload type is `view_submission`, it delegates to `async handleViewSubmission()`, which calls `await this.chat.processModalSubmit()` and accesses `payload.view.state.values` (which could throw on malformed payloads).
    

Since `routeSocketEvent` is synchronous (`void` return type) and called from a sync context within the socket mode event handler (after `await ack()` has already completed), these returned promises are fire-and-forget. If any reject, it triggers an unhandled promise rejection, which in Node.js 15+ terminates the process by default.

In contrast, in the webhook code path (`handleWebhook`), these same methods are always `return`-ed from async functions, so their promises are properly chained to the caller.

**Fix:**

Added `.catch()` handlers to both floating promises:

1.  For `handleSlashCommand`: Added `.catch()` that logs the error via `this.logger.error`.
2.  For `dispatchInteractivePayload`: Since it returns `Response | Promise<Response>` (only a Promise for `view_submission`), used `instanceof Promise` to conditionally attach a `.catch()` handler only when the result is a Promise.

This approach was chosen over making `routeSocketEvent` async because: (a) it doesn't change the method signature, (b) the caller doesn't need to await it (the ack has already been sent), and (c) errors are logged rather than silently swallowed.


Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: haydenbleasel <hello@haydenbleasel.com>
- Export SlackForwardedSocketEvent type
- Add x-slack-socket-token check at top of handleWebhook() for forwarded events
- Update routeSocketEvent() to accept WebhookOptions and use waitUntil
- Add startSocketModeListener(), runSocketModeListener(), forwardSocketEvent()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Forwarded event accepted/rejected based on appToken
- Bypasses signature verification for forwarded events
- Options passthrough to handlers
- startSocketModeListener returns 200/500 appropriately

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New /api/slack/socket-mode route using createPersistentListener
- Mirrors Discord gateway pattern (CRON_SECRET auth, Redis coordination)
- Cron runs every 9 min, listener duration 10 min

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Make signingSecret optional (string | undefined) instead of falling
back to "". verifySignature now returns false when no secret is
configured, preventing HMAC with an empty key from silently passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sync errors from processEventPayload were silently dropped in
socket mode. Wrap with try-catch for parity with slash_commands
and interactive cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dancer
Copy link
Copy Markdown
Contributor

dancer commented Apr 17, 2026

@reaganmcf do me a favour try it again 🙏🏻

@reaganmcf
Copy link
Copy Markdown

@dancer trying now!

@reaganmcf
Copy link
Copy Markdown

@dancer this is awesome!

The only thing that doesn't work is that the onModalSubmit callback doesn't support { action: "clear"} so I can't get a multi step modal to actually fully close the modal stack. I can only do { action: "close"} , which in a modal view that is from a previous { action: "push"} , will result in just going one view down the stack.

This results in:

  • Modal Step 1, submit pushes step 2
  • Modal step 2, submit sends action: "close"
  • back to Modal Step 1

Supporting the clear action should fix this. Everything else looks great and seems ready to ship 🔥

@dancer
Copy link
Copy Markdown
Contributor

dancer commented Apr 17, 2026

sweet sounds good will fix / look into that shortly and get this merged over the weekend / monday thanks for the help @reaganmcf

@doneumark
Copy link
Copy Markdown

+1

@dancer
Copy link
Copy Markdown
Contributor

dancer commented Apr 20, 2026

@reaganmcf sorry for the delay can i get you to do another quick test pushed some more changes in 925f8cf let me know & thanks a bunch for the feedback

@dancer
Copy link
Copy Markdown
Contributor

dancer commented Apr 20, 2026

after you test again i can get this merged today cc: @bensabic

@dancer dancer closed this Apr 20, 2026
@dancer dancer reopened this Apr 20, 2026
@reaganmcf
Copy link
Copy Markdown

@dancer nested slack modals work! Works great - would love to get this merged

@dancer
Copy link
Copy Markdown
Contributor

dancer commented Apr 20, 2026

SGTM let's ship it

@dancer dancer merged commit 7e90d9c into main Apr 20, 2026
11 checks passed
@dancer dancer deleted the 123-slack-socket-mode-support branch April 20, 2026 15:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Slack Socket Mode Support

7 participants