diff --git a/nodejs/test/e2e/harness/sdkTestContext.ts b/nodejs/test/e2e/harness/sdkTestContext.ts index 624450e47c..3e4c3a0560 100644 --- a/nodejs/test/e2e/harness/sdkTestContext.ts +++ b/nodejs/test/e2e/harness/sdkTestContext.ts @@ -291,16 +291,44 @@ export async function createSdkTestContext({ }); afterAll(async () => { - await copilotClient.stop(); - await openAiEndpoint.stop(anyTestFailed); + removeDisconnectedSessionEntries(copilotClient); + const stopErrors = await copilotClient.stop(); + let proxyStopError: unknown; + try { + await openAiEndpoint.stop(anyTestFailed); + } catch (error) { + proxyStopError = error; + } await rmDir("remove e2e test copilotHomeDir", copilotHomeDir); await rmDir("remove e2e test homeDir", homeDir); await rmDir("remove e2e test workDir", workDir); + if (stopErrors.length > 0) { + throw new AggregateError(stopErrors, "Copilot client cleanup failed"); + } + if (proxyStopError) { + throw proxyStopError; + } }); return harness; } +function removeDisconnectedSessionEntries(client: CopilotClient): void { + // `CopilotSession.disconnect()` leaves its object in the client's private registry. + // The in-process client stop path aborts every registry entry, including these + // already-destroyed sessions. Remove only stale test entries before teardown. + const sessions = ( + client as unknown as { + sessions: Map; + } + ).sessions; + for (const [sessionId, session] of sessions) { + if (session.disconnected) { + sessions.delete(sessionId); + } + } +} + function getTrafficCapturePath(testContext: TestContext): string { const testFilePath = testContext.task.file.filepath; const suffix = ".test.ts";