From 83eeaf5e77782600f41f4315ad6df9365039ec53 Mon Sep 17 00:00:00 2001 From: Sushanth Tiruvaipati Date: Wed, 24 Jun 2026 10:27:01 -0700 Subject: [PATCH] fix(voice): capture activity to local before awaits in closeImplInner When close() races an activity transition that sets this.activity to undefined between the if-guard and subsequent awaits, accesses like this.activity.currentSpeech throw TypeError. Capturing the reference to a local variable at the top of the block makes all reads within the if-branch consistent regardless of concurrent mutations. Fixes #1871 --- agents/src/voice/agent_session.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/agents/src/voice/agent_session.ts b/agents/src/voice/agent_session.ts index d5ac4eb2e..692bb5a36 100644 --- a/agents/src/voice/agent_session.ts +++ b/agents/src/voice/agent_session.ts @@ -1526,25 +1526,26 @@ export class AgentSession< this._onAecWarmupExpired(); this.off(AgentSessionEventTypes.UserInputTranscribed, this._onUserInputTranscribed); - if (this.activity) { + const activity = this.activity; + if (activity) { if (!drain) { try { - await this.activity.interrupt({ force: true }).await; + await activity.interrupt({ force: true }).await; } catch (error) { this.logger.warn({ error }, 'Error interrupting activity'); } } - await this.activity.drain(); + await activity.drain(); // wait any uninterruptible speech to finish - await this.activity.currentSpeech?.waitForPlayout(); + await activity.currentSpeech?.waitForPlayout(); if (reason !== CloseReason.ERROR) { - this.activity.commitUserTurn({ audioDetached: true, throwIfNotReady: false }); + activity.commitUserTurn({ audioDetached: true, throwIfNotReady: false }); } try { - this.activity.detachAudioInput(); + activity.detachAudioInput(); } catch (error) { // Ignore detach errors during cleanup - source may not have been set }