Draft
Conversation
1 task
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
…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 <[email protected]>
- 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 <[email protected]>
- 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 <[email protected]>
- 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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
Stop using the Slack app-level token (xapp-...) as the bearer token for HTTP forwarding. Adds socketForwardingSecret config option (auto-detected from SLACK_SOCKET_FORWARDING_SECRET) with fallback to appToken for backwards compatibility. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Validate body.event exists and construct a properly typed SlackWebhookPayload instead of using `as unknown as`. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Remove export — only used internally by the forwarding mechanism. Co-Authored-By: Claude Opus 4.6 <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
x-slack-socket-tokenheadermode: "webhook"(default) — socket mode listener is a separate external concernrouteSocketEvent()now acceptsWebhookOptionsand useswaitUntilfor async handlersstartSocketModeListener()/runSocketModeListener()/forwardSocketEvent()methods/api/slack/socket-modewithcreatePersistentListenerfor Redis-based cross-instance coordinationTest plan
appToken→ 200appTokenconfigured → 401startSocketModeListenerreturns 200 with valid configstartSocketModeListenerreturns 500 withoutwaitUntilorappTokenrouteSocketEventpasses options through to handlerspnpm validatepasses (knip, check, typecheck, test, build)Closes #123