From cd518a6e556c02a508231f46949fe9ada7ddb9a2 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 da73676d0..61045dad3 100644 --- a/packages/socket-mode/src/SocketModeClient.test.ts +++ b/packages/socket-mode/src/SocketModeClient.test.ts @@ -39,6 +39,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 ac2f86659..0f22c08fa 100644 --- a/packages/socket-mode/src/SocketModeClient.ts +++ b/packages/socket-mode/src/SocketModeClient.ts @@ -142,7 +142,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); @@ -228,14 +232,15 @@ 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) => { setTimeout(() => { 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 5c8a15dc620c1afad00bbec2ad6da51345d7af10 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 61045dad3..753a92aef 100644 --- a/packages/socket-mode/src/SocketModeClient.test.ts +++ b/packages/socket-mode/src/SocketModeClient.test.ts @@ -55,6 +55,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 0f22c08fa..c13a44026 100644 --- a/packages/socket-mode/src/SocketModeClient.ts +++ b/packages/socket-mode/src/SocketModeClient.ts @@ -17,6 +17,8 @@ import { SlackWebSocket, WS_READY_STATES } from './SlackWebSocket'; import type { SocketModeOptions } from './SocketModeOptions'; import { UnrecoverableSocketModeStartError } from './UnrecoverableSocketModeStartError'; +class SocketModeClientShuttingDownError extends Error {} + // Lifecycle events as described in the README enum State { Connecting = 'connecting', @@ -143,7 +145,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}`); } }); @@ -236,7 +238,7 @@ export class SocketModeClient extends EventEmitter { setTimeout(() => { 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);