{"record":{"id":"184cb372001315d3","repo":"discordjs/discord.js","slug":"cannot-destroy-voiceconnection-it-has-already-be","errorCode":null,"errorMessage":"Cannot destroy VoiceConnection - it has already been destroyed","messagePattern":"Cannot destroy VoiceConnection - it has already been destroyed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/voice/src/VoiceConnection.ts","lineNumber":586,"sourceCode":"\t * @param buffer - The Opus packet to play\n\t */\n\tpublic playOpusPacket(buffer: Buffer) {\n\t\tconst state = this.state;\n\t\tif (state.status !== VoiceConnectionStatus.Ready) return;\n\t\tstate.networking.prepareAudioPacket(buffer);\n\t\treturn state.networking.dispatchAudio();\n\t}\n\n\t/**\n\t * Destroys the VoiceConnection, preventing it from connecting to voice again.\n\t * This method should be called when you no longer require the VoiceConnection to\n\t * prevent memory leaks.\n\t *\n\t * @param adapterAvailable - Whether the adapter can be used\n\t */\n\tpublic destroy(adapterAvailable = true) {\n\t\tif (this.state.status === VoiceConnectionStatus.Destroyed) {\n\t\t\tthrow new Error('Cannot destroy VoiceConnection - it has already been destroyed');\n\t\t}\n\n\t\tif (getVoiceConnection(this.joinConfig.guildId, this.joinConfig.group) === this) {\n\t\t\tuntrackVoiceConnection(this);\n\t\t}\n\n\t\tif (adapterAvailable) {\n\t\t\tthis.state.adapter.sendPayload(createJoinVoiceChannelPayload({ ...this.joinConfig, channelId: null }));\n\t\t}\n\n\t\tthis.state = {\n\t\t\tstatus: VoiceConnectionStatus.Destroyed,\n\t\t};\n\t}\n\n\t/**\n\t * Disconnects the VoiceConnection, allowing the possibility of rejoining later on.\n\t *","sourceCodeStart":568,"sourceCodeEnd":604,"githubUrl":"https://github.com/discordjs/discord.js/blob/a81ed8a306d37fdc746e26a634b6a42164ba2c8c/packages/voice/src/VoiceConnection.ts#L568-L604","documentation":"VoiceConnection.destroy() transitions the connection to the Destroyed state and releases resources; calling it on an already-destroyed connection throws this Error, since a second teardown is invalid.","triggerScenarios":"Calling connection.destroy() twice — e.g. from both a disconnect handler and cleanup code, or after reconnection logic already destroyed the old connection.","commonSituations":"Double cleanup in shutdown paths (SIGINT handler + voiceStateUpdate), calling destroy() after subscribe/on('destroyed') already ran, or shared references to a stale connection object.","solutions":["Check connection.state.status !== VoiceConnectionStatus.Destroyed before calling destroy()","Track destruction with a boolean or set so cleanup runs at most once","Wrap destroy() in try/catch in generic cleanup code"],"exampleFix":"// before\nconnection.destroy();\n// after\nif (connection.state.status !== VoiceConnectionStatus.Destroyed) {\n  connection.destroy();\n}","handlingStrategy":"validation","validationCode":"import { VoiceConnectionStatus } from '@discordjs/voice';\nif (connection.state.status !== VoiceConnectionStatus.Destroyed) {\n  connection.destroy();\n}","typeGuard":null,"tryCatchPattern":"try {\n  connection.destroy();\n} catch (err) {\n  if (err.message.includes('already been destroyed')) {\n    // idempotent cleanup: safe to ignore\n  } else throw err;\n}","preventionTips":["Make destroy paths idempotent with a local destroyed flag or wrapper","Unsubscribe disconnect handlers before destroying","Avoid keeping long-lived references that outlive the connection's lifecycle"],"tags":["voice","lifecycle","double-destroy"],"backgroundTag":"already-destroyed","analyzedSha":"a81ed8a306d37fdc746e26a634b6a42164ba2c8c","analyzedAt":"2026-08-30T04:07:22.193Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}