From c73e2a76d1d240dc1d869705f74a7061c75e4c1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=91=E3=82=93=E3=81=A1=E3=82=8B=20/=20Kento=20Watanab?= =?UTF-8?q?e?= <65713982+KenCir@users.noreply.github.com> Date: Fri, 17 Jul 2026 08:02:47 +0900 Subject: [PATCH] fix(VoiceConnection): handle close codes that should not reconnect (#11567) * fix(VoiceConnection): handle close codes that should not reconnect * docs(VoiceConnection): update close code documentation --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../voice/__tests__/VoiceConnection.test.ts | 10 +++++----- packages/voice/src/VoiceConnection.ts | 18 ++++++++++-------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/voice/__tests__/VoiceConnection.test.ts b/packages/voice/__tests__/VoiceConnection.test.ts index 38ec562bd..7487f89ff 100644 --- a/packages/voice/__tests__/VoiceConnection.test.ts +++ b/packages/voice/__tests__/VoiceConnection.test.ts @@ -377,17 +377,17 @@ describe('VoiceConnection#onNetworkingClose', () => { expect(adapter.sendPayload).not.toHaveBeenCalled(); }); - test('Disconnects for code 4014', () => { + test.each([4_014, 4_021, 4_022])('Disconnects for close code %i', (code) => { const { voiceConnection, adapter } = createFakeVoiceConnection(); - voiceConnection['onNetworkingClose'](4_014); + voiceConnection['onNetworkingClose'](code); expect(voiceConnection.state).toMatchObject({ status: VoiceConnectionStatus.Disconnected, - closeCode: 4_014, + closeCode: code, }); expect(adapter.sendPayload).not.toHaveBeenCalled(); }); - test('Attempts rejoin for codes != 4014', () => { + test('Attempts rejoin for close codes that should reconnect', () => { const dummyPayload = Symbol('dummy') as any; const { voiceConnection, adapter, joinConfig } = createFakeVoiceConnection(); DataStore.createJoinVoiceChannelPayload.mockImplementation((config) => @@ -399,7 +399,7 @@ describe('VoiceConnection#onNetworkingClose', () => { expect(voiceConnection.rejoinAttempts).toEqual(1); }); - test('Attempts rejoin for codes != 4014 (with adapter failure)', () => { + test('Attempts rejoin for close codes that should reconnect (with adapter failure)', () => { const dummyPayload = Symbol('dummy') as any; const { voiceConnection, adapter, joinConfig } = createFakeVoiceConnection(); DataStore.createJoinVoiceChannelPayload.mockImplementation((config) => diff --git a/packages/voice/src/VoiceConnection.ts b/packages/voice/src/VoiceConnection.ts index b3c73c813..aeb5f01a1 100644 --- a/packages/voice/src/VoiceConnection.ts +++ b/packages/voice/src/VoiceConnection.ts @@ -451,21 +451,23 @@ export class VoiceConnection extends EventEmitter { } /** - * Called when the networking instance for this connection closes. If the close code is 4014 (do not reconnect), - * the voice connection will transition to the Disconnected state which will store the close code. You can - * decide whether or not to reconnect when this occurs by listening for the state change and calling reconnect(). + * Called when the networking instance for this connection closes. If the close code is 4014, 4021, or 4022 + * (do not reconnect), the voice connection will transition to the Disconnected state which will store the + * close code. You can decide whether or not to reconnect when this occurs by listening for the state change + * and calling reconnect(). * * @remarks - * If the close code was anything other than 4014, it is likely that the closing was not intended, and so the - * VoiceConnection will signal to Discord that it would like to rejoin the channel. This automatically attempts - * to re-establish the connection. This would be seen as a transition from the Ready state to the Signalling state. + * If the close code was anything other than 4014, 4021, or 4022, it is likely that the closing was not intended, + * and so the VoiceConnection will signal to Discord that it would like to rejoin the channel. This automatically + * attempts to re-establish the connection. This would be seen as a transition from the Ready state to the + * Signalling state. * @param code - The close code */ private onNetworkingClose(code: number) { if (this.state.status === VoiceConnectionStatus.Destroyed) return; // If networking closes, try to connect to the voice channel again. - if (code === 4_014) { - // Disconnected - networking is already destroyed here + if (code === 4_014 || code === 4_021 || code === 4_022) { + // Disconnected - these close codes should not trigger a reconnect this.state = { ...this.state, status: VoiceConnectionStatus.Disconnected,