Repository navigation
fix(socket-mode): stop waiting on a close handshake Slack never completes - #2819
Conversation
…etes undici marks the socket CLOSING when the close handshake completes but only fires "close" once the peer ends TCP, which Slack never does. disconnect() therefore waited for the pong watchdog (about clientPingTimeout) or the 30s fallback timer. - Lower the close-handshake timeout to 1s and log it at debug: it is the expected path against Slack. - Stop the ping/pong heartbeat once the close frame is sent, so the timeout owns the wait and the spurious "A pong wasn't received" warning is gone. - When Slack starts the close handshake, terminate on the next heartbeat tick; undici's ping() silently does nothing while CLOSING. Fixes #2752. Supersedes #2697. Co-authored-by: Grmiade <jeremy@payfit.com> Co-authored-by: Claude <svc-devxp-claude@slack-corp.com>
🦋 Changeset detectedLatest commit: 76c94cc The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2819 +/- ##
=======================================
Coverage 93.89% 93.90%
=======================================
Files 66 66
Lines 10571 10581 +10
Branches 611 611
=======================================
+ Hits 9926 9936 +10
- Misses 609 611 +2
+ Partials 36 34 -2
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
…ake short The test still passed with stopMonitoringPings() removed from disconnect(), since the heartbeat's CLOSING check cleaned up early with no ping or warn. Asserting no close before the 1s timeout guards the ack window, and the test name now says what it checks. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>
zimeg
left a comment
There was a problem hiding this comment.
🔌 @WilliamBergamin LGTM! Stopping the heartbeat once the close frame is sent is a clean fix, and thanks for the tests that fail on main ✨
mwbrooks
left a comment
There was a problem hiding this comment.
✅ Thanks @WilliamBergamin! Taking app.stop() from 5s down to about 1s is a lovely quality-of-life fix for Bolt apps.
| this.logger.error('WebSocket not available, skipping ping.'); | ||
| return; | ||
| } | ||
| if (this.websocket.readyState === WebSocket.CLOSING) { |
There was a problem hiding this comment.
praise: Terminating on the next heartbeat tick is a neat answer to undici's silent ping() while CLOSING, and disconnect() already routes that state straight to cleanup().
Summary
This pull request stops
disconnect()from waiting 5 seconds or more on a close handshake that Slack never completes.Fixes #2752. Supersedes #2697 (thanks @Grmiade, credited as co-author).
The cause: undici marks the socket
CLOSINGonce the close frames are exchanged, but only firesclosewhen the peer ends TCP. Slack never does, sodisconnect()waited until the pong watchdog gave up (aboutclientPingTimeout, with theA pong wasn't receivedwarning) or until the 30s fallback timer.ping()silently does nothing whileCLOSING, so before this only the watchdog caught it.Why 1s and not 0: the wait lets acks queued just before
disconnect()(boltack()thenapp.stop()) reach Slack before the socket is destroyed, and it still works with a customdispatcher, where the SDK can't reach the underlying socket.Testing
The updated and new unit tests fail on
main.To check by hand against a build of this branch:
slack-shutdown.mjsfrom SocketModeClient.disconnect() takes ~5 seconds to close on 3.0.1 #2752 with a realxapp-token and press Ctrl+C:disconnect()resolves in about 1s with no pong warning (5 to 6.6s onmain).main, 1.00s with no warning on this branch.Requirements
🤖 Generated with Claude Code