diff --git a/.changeset/metal-workers-exit.md b/.changeset/metal-workers-exit.md new file mode 100644 index 000000000..b628ffa23 --- /dev/null +++ b/.changeset/metal-workers-exit.md @@ -0,0 +1,5 @@ +--- +"@livekit/agents": patch +--- + +Fix worker cleanup after LiveKit connection retries are exhausted. diff --git a/agents/src/worker.test.ts b/agents/src/worker.test.ts new file mode 100644 index 000000000..5b8f4c9dc --- /dev/null +++ b/agents/src/worker.test.ts @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2026 LiveKit, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +import { describe, expect, it, vi } from 'vitest'; +import { AgentServer, ServerOptions } from './worker.js'; + +vi.mock('./inference/_warmup.js', () => ({ + _getLocalInferenceModule: () => undefined, +})); + +describe('AgentServer connection failures', () => { + it('run rejects when connection retries are exhausted', async () => { + const server = new AgentServer( + new ServerOptions({ + agent: 'test-agent.js', + wsURL: 'ws://127.0.0.1:1', + apiKey: 'devkey', + apiSecret: 'devsecret', + maxRetry: 0, + numIdleProcesses: 0, + simulation: true, + }), + ); + + try { + await expect(server.run()).rejects.toThrow(/failed to connect/); + } finally { + await server.close(); + await server.close(); + } + }); +}); diff --git a/agents/src/worker.ts b/agents/src/worker.ts index e08ff2ef4..57f046559 100644 --- a/agents/src/worker.ts +++ b/agents/src/worker.ts @@ -491,8 +491,15 @@ export class AgentServer { if (this.#httpServer) { tasks.push(this.#httpServer.run()); } - await ThrowsPromise.all(tasks); - this.#close.resolve(); + try { + await ThrowsPromise.all(tasks); + this.#close.resolve(); + } catch (e) { + if (!this.#close.done) { + this.#close.reject(e instanceof Error ? e : new Error(String(e))); + } + throw e; + } } get id(): string { @@ -925,7 +932,7 @@ export class AgentServer { async close() { if (this.#closed) { - await this.#close.await; + await this.#close.await.catch(() => undefined); return; } @@ -939,7 +946,7 @@ export class AgentServer { await ThrowsPromise.allSettled(this.#tasks); this.#session?.close(); - await this.#close.await; + await this.#close.await.catch(() => undefined); } }