From efffa5ca85ded8abaa81284da985b2f817b5c44f Mon Sep 17 00:00:00 2001 From: w3lld1 <42353747+w3lld1@users.noreply.github.com> Date: Sun, 12 Jul 2026 13:35:35 +0000 Subject: [PATCH 1/2] fix(socket-mode): observe reconnect rejections --- .../socket-mode/src/SocketModeClient.test.ts | 18 ++++++++++++++++++ packages/socket-mode/src/SocketModeClient.ts | 11 ++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/socket-mode/src/SocketModeClient.test.ts b/packages/socket-mode/src/SocketModeClient.test.ts index 368644556..4e8a5a6a9 100644 --- a/packages/socket-mode/src/SocketModeClient.test.ts +++ b/packages/socket-mode/src/SocketModeClient.test.ts @@ -83,6 +83,24 @@ describe('SocketModeClient', () => { it('should resolve once Disconnected state emitted'); }); + describe('reconnect', () => { + it('should observe a rejected reconnect attempt', async () => { + const logger = new ConsoleLogger(); + const errorLog = sandbox.stub(logger, 'error'); + const client = new SocketModeClient({ + appToken: 'xapp-', + clientPingTimeout: 1, + logger, + }); + sandbox.stub(client, 'start').rejects(new Error('reconnect failed')); + + client.emit('close'); + await sleep(10); + + sinon.assert.calledWith(errorLog, 'Failed to reconnect Socket Mode client: Error: reconnect failed'); + }); + }); + describe('onWebSocketMessage', () => { // While this method is protected and cannot be invoked directly, emitting the 'message' event directly invokes it describe('slash_commands messages', () => { diff --git a/packages/socket-mode/src/SocketModeClient.ts b/packages/socket-mode/src/SocketModeClient.ts index 9f07c58c4..8838bac3d 100644 --- a/packages/socket-mode/src/SocketModeClient.ts +++ b/packages/socket-mode/src/SocketModeClient.ts @@ -158,7 +158,11 @@ export class SocketModeClient extends EventEmitter { this.on('close', () => { // Underlying WebSocket connection was closed, possibly reconnect. if (!this.shuttingDown && this.autoReconnectEnabled) { - this.delayReconnectAttempt(this.start); + void this.delayReconnectAttempt(this.start).catch((error) => { + if (!this.shuttingDown) { + this.logger.error(`Failed to reconnect Socket Mode client: ${error}`); + } + }); } else { // If reconnect is disabled or user explicitly called `disconnect()`, emit a disconnected state. this.emit(State.Disconnected); @@ -246,15 +250,16 @@ export class SocketModeClient extends EventEmitter { this.numOfConsecutiveReconnectionFailures += 1; const msBeforeRetry = this.clientPingTimeoutMS * this.numOfConsecutiveReconnectionFailures; this.logger.debug(`Before trying to reconnect, this client will wait for ${msBeforeRetry} milliseconds`); - return new Promise((res, _rej) => { + return new Promise((resolve, reject) => { this.reconnectionTimer = setTimeout(() => { this.reconnectionTimer = undefined; if (this.shuttingDown) { this.logger.debug('Client shutting down, will not attempt reconnect.'); + resolve(undefined as T); } else { this.logger.debug('Continuing with reconnect...'); this.emit(State.Reconnecting); - cb.apply(this).then(res); + cb.apply(this).then(resolve, reject); } }, msBeforeRetry); }); From 5f0cae4690514dab60875bb8ec07140101497b07 Mon Sep 17 00:00:00 2001 From: w3lld1 <42353747+w3lld1@users.noreply.github.com> Date: Mon, 13 Jul 2026 20:34:25 +0200 Subject: [PATCH 2/2] fix(socket-mode): handle shutdown reconnect race Signed-off-by: w3lld1 <42353747+w3lld1@users.noreply.github.com> --- .../socket-mode/src/SocketModeClient.test.ts | 18 ++++++++++++++++++ packages/socket-mode/src/SocketModeClient.ts | 6 ++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/socket-mode/src/SocketModeClient.test.ts b/packages/socket-mode/src/SocketModeClient.test.ts index 4e8a5a6a9..cf5d24b1a 100644 --- a/packages/socket-mode/src/SocketModeClient.test.ts +++ b/packages/socket-mode/src/SocketModeClient.test.ts @@ -99,6 +99,24 @@ describe('SocketModeClient', () => { sinon.assert.calledWith(errorLog, 'Failed to reconnect Socket Mode client: Error: reconnect failed'); }); + + it('should not reconnect or log an error while shutting down', async () => { + const logger = new ConsoleLogger(); + const errorLog = sandbox.stub(logger, 'error'); + const client = new SocketModeClient({ + appToken: 'xapp-', + clientPingTimeout: 1, + logger, + }); + const start = sandbox.stub(client, 'start').resolves({}); + + client.emit('close'); + await client.disconnect(); + await sleep(10); + + sinon.assert.notCalled(start); + sinon.assert.notCalled(errorLog); + }); }); describe('onWebSocketMessage', () => { diff --git a/packages/socket-mode/src/SocketModeClient.ts b/packages/socket-mode/src/SocketModeClient.ts index 8838bac3d..6ef886913 100644 --- a/packages/socket-mode/src/SocketModeClient.ts +++ b/packages/socket-mode/src/SocketModeClient.ts @@ -18,6 +18,8 @@ import { SlackWebSocket, WS_READY_STATES } from './SlackWebSocket'; import type { SocketModeDispatcher, SocketModeOptions } from './SocketModeOptions'; import { UnrecoverableSocketModeStartError } from './UnrecoverableSocketModeStartError'; +class SocketModeClientShuttingDownError extends Error {} + // Lifecycle events as described in the README enum State { Connecting = 'connecting', @@ -159,7 +161,7 @@ export class SocketModeClient extends EventEmitter { // Underlying WebSocket connection was closed, possibly reconnect. if (!this.shuttingDown && this.autoReconnectEnabled) { void this.delayReconnectAttempt(this.start).catch((error) => { - if (!this.shuttingDown) { + if (!(error instanceof SocketModeClientShuttingDownError)) { this.logger.error(`Failed to reconnect Socket Mode client: ${error}`); } }); @@ -255,7 +257,7 @@ export class SocketModeClient extends EventEmitter { this.reconnectionTimer = undefined; if (this.shuttingDown) { this.logger.debug('Client shutting down, will not attempt reconnect.'); - resolve(undefined as T); + reject(new SocketModeClientShuttingDownError('Socket Mode client is shutting down')); } else { this.logger.debug('Continuing with reconnect...'); this.emit(State.Reconnecting);