From e6178056fb10ee9eab4a7cc6b1774dbd1cbb8883 Mon Sep 17 00:00:00 2001 From: Prekshi Vyas Date: Wed, 19 Aug 2026 21:16:33 -0700 Subject: [PATCH 1/2] fix(inference): stop llama.cpp bridge on destroy Signed-off-by: Prekshi Vyas --- .../local-model-profile/cleanup.test.ts | 48 ++++++++++++++++++- .../inference/local-model-profile/cleanup.ts | 8 ++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/lib/inference/local-model-profile/cleanup.test.ts b/src/lib/inference/local-model-profile/cleanup.test.ts index 54cccdd109d..7498c0df139 100644 --- a/src/lib/inference/local-model-profile/cleanup.test.ts +++ b/src/lib/inference/local-model-profile/cleanup.test.ts @@ -665,26 +665,72 @@ describe("host-local model cleanup", () => { ); }); - it("uses gateway and sandbox scope and leaves a different owner untouched", () => { + it("stops the exact bridge before sandbox cleanup and leaves a different owner untouched (#9598)", () => { const homeDir = temporaryHome(); const gatewayPort = 8091; const harness = engineHarness(); + const privateBridge = privateBridgeFixture(); createManagedState(homeDir, harness.engine, { gatewayPort }); const skipped = cleanupManagedLlamaCppRuntimeForSandbox("different-sandbox", { homeDir, gatewayPort, engine: harness.engine, + privateBridge, }); expect(skipped).toEqual({ ok: true, removed: [], preserved: [] }); expect(harness.capture).not.toHaveBeenCalled(); + expect(privateBridge.stopTransaction).not.toHaveBeenCalled(); + expect(privateBridge.assertStopped).not.toHaveBeenCalled(); const removed = cleanupManagedLlamaCppRuntimeForSandbox("spark-agent", { homeDir, gatewayPort, engine: harness.engine, + privateBridge, }); expect(removed).toMatchObject({ ok: true }); + expect(privateBridge.stopTransaction).toHaveBeenCalledWith(TRANSACTION_ID); + expect(privateBridge.assertStopped).toHaveBeenCalledWith(TRANSACTION_ID); + const containerRemovalCall = harness.capture.mock.calls.findIndex( + ([argv]) => argv[0] === "rm" && argv[1] === "--force", + ); + expect(containerRemovalCall).toBeGreaterThanOrEqual(0); + expect(privateBridge.stopTransaction.mock.invocationCallOrder[0]).toBeLessThan( + harness.capture.mock.invocationCallOrder[containerRemovalCall]!, + ); + expect(privateBridge.assertStopped.mock.invocationCallOrder[0]).toBeLessThan( + harness.capture.mock.invocationCallOrder[containerRemovalCall]!, + ); expect(fs.existsSync(managedLlamaCppStatePaths(homeDir, gatewayPort).stateDir)).toBe(false); }); + + it("preserves lifecycle authority when the sandbox bridge remains active (#9598)", () => { + const homeDir = temporaryHome(); + const harness = engineHarness(); + const privateBridge = privateBridgeFixture(); + privateBridge.assertStopped.mockImplementationOnce(() => { + throw new Error("bridge remains active"); + }); + createManagedState(homeDir, harness.engine); + + const result = cleanupManagedLlamaCppRuntimeForSandbox("spark-agent", { + homeDir, + engine: harness.engine, + privateBridge, + }); + + expect(result).toMatchObject({ ok: false, reason: "bridge remains active" }); + expect(privateBridge.stopTransaction).toHaveBeenCalledWith(TRANSACTION_ID); + expect(privateBridge.assertStopped).toHaveBeenCalledWith(TRANSACTION_ID); + expect(harness.capture).not.toHaveBeenCalledWith( + ["rm", "--force", RUNTIME_ID], + expect.any(Number), + ); + expect(harness.capture).not.toHaveBeenCalledWith( + ["network", "rm", NETWORK_ID], + expect.any(Number), + ); + expect(fs.existsSync(managedLlamaCppStatePaths(homeDir).stateDir)).toBe(true); + }); }); diff --git a/src/lib/inference/local-model-profile/cleanup.ts b/src/lib/inference/local-model-profile/cleanup.ts index 4b5df55cec2..751e0adf733 100644 --- a/src/lib/inference/local-model-profile/cleanup.ts +++ b/src/lib/inference/local-model-profile/cleanup.ts @@ -469,6 +469,7 @@ function cleanupLlamaCpp( sandboxName?: string; env?: NodeJS.ProcessEnv; engine?: ContainerEngine; + privateBridge?: DockerLlamaCppPrivateBridgeController; } = {}, ): boolean { const paths = managedLlamaCppStatePaths(homeDir, options.gatewayPort); @@ -556,6 +557,11 @@ function cleanupLlamaCpp( const lease = journalStore.acquireExecution(journal.transactionId); try { + journalStore.assertExecution(lease); + if (options.privateBridge) { + options.privateBridge.stopTransaction(journal.transactionId); + options.privateBridge.assertStopped(journal.transactionId); + } journalStore.assertExecution(lease); removeExactContainerForJournal(engine, journal, removed); journalStore.assertExecution(lease); @@ -628,6 +634,7 @@ export interface ManagedLlamaCppSandboxCleanupOptions { readonly env?: NodeJS.ProcessEnv; readonly engine?: ContainerEngine; readonly deps?: Partial; + readonly privateBridge?: DockerLlamaCppPrivateBridgeController; } export interface ManagedLlamaCppLifecycleCleanupOptions extends ManagedLlamaCppSandboxCleanupOptions { @@ -906,6 +913,7 @@ export function cleanupManagedLlamaCppRuntimeForSandbox( sandboxName, env: options.env, engine: options.engine, + privateBridge: options.privateBridge ?? createDockerLlamaCppPrivateBridgeController(), }); preserveSharedHuggingFaceCache(homeDir, preserved); return { ok: true, removed, preserved }; From 5b039077359cbcddec04b4d5ac61d792d07735b0 Mon Sep 17 00:00:00 2001 From: Prekshi Vyas Date: Wed, 19 Aug 2026 21:34:09 -0700 Subject: [PATCH 2/2] test(inference): assert bridge stop ordering Signed-off-by: Prekshi Vyas --- src/lib/inference/local-model-profile/cleanup.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/inference/local-model-profile/cleanup.test.ts b/src/lib/inference/local-model-profile/cleanup.test.ts index 5d987e8b5b0..99d3ddb0586 100644 --- a/src/lib/inference/local-model-profile/cleanup.test.ts +++ b/src/lib/inference/local-model-profile/cleanup.test.ts @@ -707,6 +707,9 @@ describe("host-local model cleanup", () => { expect(privateBridge.stopTransaction.mock.invocationCallOrder[0]).toBeLessThan( harness.capture.mock.invocationCallOrder[containerRemovalCall]!, ); + expect(privateBridge.stopTransaction.mock.invocationCallOrder[0]).toBeLessThan( + privateBridge.assertStopped.mock.invocationCallOrder[0]!, + ); expect(privateBridge.assertStopped.mock.invocationCallOrder[0]).toBeLessThan( harness.capture.mock.invocationCallOrder[containerRemovalCall]!, );