fix(slack): pre-set shuttingDown before app.stop() to prevent orphaned ping intervals#56646
Conversation
Greptile SummaryThis PR introduces The fix is minimal and targeted:
The approach correctly relies on Confidence Score: 5/5Safe to merge — the fix is a well-scoped workaround for a confirmed race condition, with no regressions in HTTP mode and consistent teardown behavior across all stop paths. No P0 or P1 issues found. The private-API access via type cast is intentional and guarded by optional chaining; it degrades silently if the library restructures its internals rather than breaking anything. The No files require special attention.
|
| Filename | Overview |
|---|---|
| extensions/slack/src/monitor/provider.ts | Adds gracefulStop() that pre-sets SocketModeClient.shuttingDown = true before calling app.stop(), closing a race window that caused orphaned ping intervals. Applied consistently to all three stop paths. |
Reviews (1): Last reviewed commit: "fix(slack): pre-set shuttingDown before ..." | Re-trigger Greptile
|
We're hitting this exact issue in production with 11 Slack accounts configured in socket mode. Symptoms observed:
Environment:
Current workaround: Increased Would appreciate this fix landing soon — it's a significant stability issue for multi-account Slack deployments. Happy to test a beta if one becomes available. |
|
There's no dedicated Slack maintainer yet. We'd appreciate it if @frankekn would help move this along. |
…d ping intervals Fixes openclaw#56508 When the health monitor restarts a stale Slack socket-mode connection, there's a race condition in @slack/[email protected]: 1. Health monitor calls stopChannel() → abort signal → app.stop() 2. app.stop() → SocketModeClient.disconnect() sets shuttingDown = true 3. But the library's internal ping interval may fire first, calling SlackWebSocket.disconnect() → terminate() → emits 'close' 4. The close handler checks shuttingDown (still false) and schedules an orphaned reconnect via delayReconnectAttempt(this.start) 5. The orphaned connection's ping interval keeps firing indefinitely, logging 'A pong wasn't received...' every 1.6s and leaking FDs Fix: introduce gracefulStop() that sets shuttingDown = true on the SocketModeClient BEFORE calling app.stop(), closing the race window. Applied to all three stop paths: abort handler, reconnect loop, and finally block. Single file change. No new dependencies.
b6c3b14 to
f1c91d5
Compare
…d ping intervals (openclaw#56646) Merged via squash with admin override. Prepared head SHA: f1c91d5 Note: required red lanes are currently inherited from latest origin/main, not introduced by this PR. Co-authored-by: hsiaoa <[email protected]> Co-authored-by: frankekn <[email protected]> Reviewed-by: @frankekn
…d ping intervals (openclaw#56646) Merged via squash with admin override. Prepared head SHA: f1c91d5 Note: required red lanes are currently inherited from latest origin/main, not introduced by this PR. Co-authored-by: hsiaoa <[email protected]> Co-authored-by: frankekn <[email protected]> Reviewed-by: @frankekn
…d ping intervals (openclaw#56646) Merged via squash with admin override. Prepared head SHA: f1c91d5 Note: required red lanes are currently inherited from latest origin/main, not introduced by this PR. Co-authored-by: hsiaoa <[email protected]> Co-authored-by: frankekn <[email protected]> Reviewed-by: @frankekn
…d ping intervals (openclaw#56646) Merged via squash with admin override. Prepared head SHA: f1c91d5 Note: required red lanes are currently inherited from latest origin/main, not introduced by this PR. Co-authored-by: hsiaoa <[email protected]> Co-authored-by: frankekn <[email protected]> Reviewed-by: @frankekn
…d ping intervals (openclaw#56646) Merged via squash with admin override. Prepared head SHA: f1c91d5 Note: required red lanes are currently inherited from latest origin/main, not introduced by this PR. Co-authored-by: hsiaoa <[email protected]> Co-authored-by: frankekn <[email protected]> Reviewed-by: @frankekn
Fixes #56508
Problem
When the health monitor restarts a stale Slack socket-mode connection, a race condition in
@slack/[email protected]can leave orphaned ping intervals running indefinitely:stopChannel()→ aborts →app.stop()app.stop()→SocketModeClient.disconnect()setsshuttingDown = trueSlackWebSocket.disconnect()→terminate()→ emitscloseBEFORE step 2 setsshuttingDownclosehandler seesshuttingDown === false→ schedulesdelayReconnectAttempt(this.start)"A pong wasn't received..."every ~1.6s and leaking file descriptorsImpact:
gateway.err.loggrows unbounded (~3 warnings/1.6s per account), FD accumulation over time.Fix
Introduce
gracefulStop()that setsshuttingDown = trueon theSocketModeClientBEFORE callingapp.stop(), closing the race window. Applied to all three stop paths:stopOnAborthandler (health monitor restart)finallyblock (provider teardown)Why not fix upstream?
@slack/[email protected]has no fix for this (only cosmetic async/await changes). The proper upstream fix would be to clear ping intervals inSocketModeClient.disconnect()before callingwebsocket.disconnect(), but that requires a library release. This workaround is safe becauseshuttingDownis a simple boolean sentinel — setting it early just makes the close handler skip the reconnect, which is exactly what we want.Testing
stale-socketrestarts no longer produce orphaned pong warningsgateway.err.logstays clean after restart cyclesAI Disclosure
🤖 AI-assisted (Kiro CLI). Root cause analysis from issue #56508, fix verified against
@slack/socket-modesource.