From e05b42a19f4defc2a42fa4387b9c3e7d9e15a0cf Mon Sep 17 00:00:00 2001 From: N0zoM1z0 <161784452+N0zoM1z0@users.noreply.github.com> Date: Sun, 11 Oct 2026 02:51:36 +0800 Subject: [PATCH 1/2] perf(evm): retain cleanup diagnostics without repeated output --- src/evm/EvmoleFailures.ts | 2 - src/evm/EvmoleInterfaceProvider.ts | 24 +--------- .../evm/evmoleInterfaceProvider.test.ts | 46 +++++++++++-------- 3 files changed, 29 insertions(+), 43 deletions(-) diff --git a/src/evm/EvmoleFailures.ts b/src/evm/EvmoleFailures.ts index 5690a079ab8..2677a3e719f 100644 --- a/src/evm/EvmoleFailures.ts +++ b/src/evm/EvmoleFailures.ts @@ -59,8 +59,6 @@ export const evmInterfaceFailure = ( message: cause.message, exit_code: cause.snapshot?.exitCode ?? null, signal: cause.snapshot?.signal ?? null, - stdout: cause.snapshot?.stdout.text ?? null, - stderr: cause.snapshot?.stderr.text ?? null, }, }, { operation: "inspect_evm_interface", cause }, diff --git a/src/evm/EvmoleInterfaceProvider.ts b/src/evm/EvmoleInterfaceProvider.ts index 42a4e08480d..03731a92a6c 100644 --- a/src/evm/EvmoleInterfaceProvider.ts +++ b/src/evm/EvmoleInterfaceProvider.ts @@ -401,28 +401,8 @@ export class EvmoleInterfaceProvider implements EvmInterfacePort { } if (processOwner !== undefined) { this.#pendingCleanup = { root, process: processOwner }; - root = undefined; - return err( - new ProviderCleanupError( - EVMOLE_PROVIDER_IDENTITY.id, - [ - processOwner.launch.ownership?.runId ?? "owned-worker", - ...(this.#pendingCleanup.root === undefined - ? [] - : [this.#pendingCleanup.root.path]), - ], - { - reason: "Owned EVM worker cleanup could not be confirmed", - previous_error: result.ok - ? null - : projectAnalysisError(result.error), - ...(retainedOutput === undefined - ? {} - : { captured_output: { ...retainedOutput } }), - }, - { operation: OPERATION }, - ), - ); + // The command failure already owns this cleanup diagnostic and its resources. + return result; } if (root !== undefined) { try { diff --git a/tests/boundary/evm/evmoleInterfaceProvider.test.ts b/tests/boundary/evm/evmoleInterfaceProvider.test.ts index 708a6fc62dc..8956c5b0f8e 100644 --- a/tests/boundary/evm/evmoleInterfaceProvider.test.ts +++ b/tests/boundary/evm/evmoleInterfaceProvider.test.ts @@ -130,7 +130,9 @@ it.runIf(!unsupportedHost)( const { path } = await fixture(); let ownedPath = ""; let failCleanup = true; + let launches = 0; const provider = new EvmoleInterfaceProvider({}, async (spawn) => { + launches += 1; ownedPath = spawn.cwd ?? ""; const launched = await spawnOwnedProviderProcess({ ...spawn, @@ -158,36 +160,42 @@ it.runIf(!unsupportedHost)( if (result.ok) throw new Error("Expected compounded lifecycle failure"); expect(result.error.cleanupIncomplete).toBe(true); // Inspect the retained error chain without diffing a megabyte of stdout. - const output = z.object({ truncated: z.boolean(), stderr: z.string() }); const cleanup = z .object({ reason: z.string(), - previous_error: z.object({ - code: z.string(), - details: z.object({ - diagnostics: z.object({ - reason: z.string(), - previous_error: z.object({ failure_kind: z.string() }), - captured_output: output, - }), - }), + previous_error: z + .object({ + failure_kind: z.string(), + stdout: z.unknown().optional(), + stderr: z.unknown().optional(), + }) + .transform(({ failure_kind, stdout, stderr }) => ({ + failure_kind, + output_repeated: stdout !== undefined || stderr !== undefined, + })), + captured_output: z.object({ + truncated: z.boolean(), + stderr: z.literal(""), + stdout: z.string().transform((value) => Buffer.byteLength(value)), }), }) .parse(projectAnalysisError(result.error).details?.["diagnostics"]); expect(cleanup).toEqual({ - reason: "Owned EVM worker cleanup could not be confirmed", + reason: "injected post-cleanup reporting failure", previous_error: { - code: "cleanup_incomplete", - details: { - diagnostics: { - reason: "injected post-cleanup reporting failure", - previous_error: { failure_kind: "output-limit" }, - captured_output: { truncated: true, stderr: "" }, - }, - }, + failure_kind: "output-limit", + output_repeated: false, }, + captured_output: { truncated: true, stderr: "", stdout: 1048576 }, }); + expect(result.error.cleanupResources).toContain(ownedPath); await access(ownedPath); + const blocked = await provider.inspect({ path, encoding: "hex" }); + expect(blocked.ok).toBe(false); + expect(launches).toBe(1); + await expect(provider.close()).rejects.toMatchObject({ + cleanupIncomplete: true, + }); failCleanup = false; await provider.close(); await expect(access(ownedPath)).rejects.toMatchObject({ code: "ENOENT" }); From 0ab1e7f7afe260f5e69802a809bbf5e06fc4cb7e Mon Sep 17 00:00:00 2001 From: N0zoM1z0 <161784452+N0zoM1z0@users.noreply.github.com> Date: Sun, 11 Oct 2026 02:55:36 +0800 Subject: [PATCH 2/2] test(browser): verify released sockets after cancelled cleanup Incorporate the connection-release controls from #1617 while retaining the independent detach regression. --- tests/boundary/browser/cdpCleanupCancellation.test.ts | 8 ++++++++ tests/fixtures/fakeCdpBrowser.ts | 1 + tests/fixtures/fakeCdpBrowserTypes.ts | 2 ++ 3 files changed, 11 insertions(+) diff --git a/tests/boundary/browser/cdpCleanupCancellation.test.ts b/tests/boundary/browser/cdpCleanupCancellation.test.ts index 0d7075ab6bb..0d4c17a0fa9 100644 --- a/tests/boundary/browser/cdpCleanupCancellation.test.ts +++ b/tests/boundary/browser/cdpCleanupCancellation.test.ts @@ -47,6 +47,7 @@ it.each(["Page.disable", "Target.detachFromTarget"])( expect(methods).toContain("Target.detachFromTarget"); expect(methods).not.toContain("Target.closeTarget"); expect(methods).not.toContain("Browser.close"); + await vi.waitFor(() => expect(browser.activeConnectionCount()).toBe(0)); }, ); @@ -76,6 +77,11 @@ it("releases cleanup when cancellation arrives after capture completes", async ( expect(browser.commands.map(({ method }) => method)).toContain( "Target.detachFromTarget", ); + await vi.waitFor(() => expect(browser.activeConnectionCount()).toBe(0)); + const methods = browser.commands.map(({ method }) => method); + expect(methods).not.toContain("Target.closeTarget"); + expect(methods).not.toContain("Browser.close"); + expect((await fetch(`${browser.endpoint}/json/version`)).ok).toBe(true); }); it("closes an Electron inspection session when cleanup is cancelled", async () => { @@ -127,4 +133,6 @@ it("closes an Electron inspection session when cleanup is cancelled", async () = expect(methods).toContain("Target.detachFromTarget"); expect(methods).not.toContain("Target.closeTarget"); expect(methods).not.toContain("Browser.close"); + await vi.waitFor(() => expect(browser.activeConnectionCount()).toBe(0)); + expect((await fetch(`${browser.endpoint}/json/version`)).ok).toBe(true); }); diff --git a/tests/fixtures/fakeCdpBrowser.ts b/tests/fixtures/fakeCdpBrowser.ts index c085b180625..5c99f1fb0f3 100644 --- a/tests/fixtures/fakeCdpBrowser.ts +++ b/tests/fixtures/fakeCdpBrowser.ts @@ -143,6 +143,7 @@ export const startFakeCdpBrowser = async ( browserWebSocketUrl: `ws://127.0.0.1:${String(port)}/devtools/browser/fake`, allowedOrigin: endpoint, commands, + activeConnectionCount: () => sockets.size, emitRawMessage(message) { for (const socket of sockets) socket.send(message); }, diff --git a/tests/fixtures/fakeCdpBrowserTypes.ts b/tests/fixtures/fakeCdpBrowserTypes.ts index 54fc7656320..e109178147a 100644 --- a/tests/fixtures/fakeCdpBrowserTypes.ts +++ b/tests/fixtures/fakeCdpBrowserTypes.ts @@ -11,6 +11,8 @@ export interface FakeCdpBrowser { readonly browserWebSocketUrl: string; readonly allowedOrigin: string; readonly commands: readonly FakeCdpCommand[]; + /** Actual WebSocket connections still held by the wire fixture. */ + activeConnectionCount(): number; /** Deliver actual malformed wire data without bypassing the production transport parser. */ emitRawMessage(message: string): void; emitEvent(event: {