From 7cef8ef43bf303c7951c9cacdb878e4a842ea5de Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 30 Oct 2026 11:31:30 +0100 Subject: [PATCH] fix(app): run isolated daemon cleanup outside the test loader Importing the server lifecycle into the browser test runner exposes its transitive plugin exports to the runner's transforming loader. Use the supported CLI in ordinary Node so confirmed shutdown and terminal reaping complete before removing the owned home. --- .../support/helpers/isolated-host-daemon.ts | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/packages/app/e2e/support/helpers/isolated-host-daemon.ts b/packages/app/e2e/support/helpers/isolated-host-daemon.ts index 85f88bc164..c8dd3eaa44 100644 --- a/packages/app/e2e/support/helpers/isolated-host-daemon.ts +++ b/packages/app/e2e/support/helpers/isolated-host-daemon.ts @@ -1,7 +1,11 @@ -import { pathToFileURL } from "node:url"; -import type { DaemonClient } from "@getpaseo/client/internal/daemon-client"; -import { connectDaemonClient } from "./daemon-client-loader"; -import { spawn, execFileSync, type ChildProcess, type SpawnOptions } from "node:child_process"; +import { promisify } from "node:util"; +import { + spawn, + execFile, + execFileSync, + type ChildProcess, + type SpawnOptions, +} from "node:child_process"; import { mkdtemp, rm, writeFile } from "node:fs/promises"; import net from "node:net"; import { tmpdir } from "node:os"; @@ -9,6 +13,8 @@ import path from "node:path"; import { withDisabledE2ESpeechEnv } from "./speech-env"; import { killProcessTree, spawnTsx } from "./spawn-node"; +const execFileAsync = promisify(execFile); + export interface IsolatedHostDaemon { serverId: string; port: number; @@ -157,30 +163,20 @@ export async function startIsolatedHostDaemon( : path.resolve(__dirname, "../../../../server"); let child: ChildProcess | null = null; const stopDaemon = async (): Promise => { - // The daemon lifecycle owns its worker and detached PTYs. Waiting only for - // the supervisor lets a surviving worker recreate the home during removal. - // Load the ESM lifecycle entry natively, as daemon-client-loader does for - // the SDK; Playwright's CommonJS transform cannot load its createRequire. - const lifecycleUrl = pathToFileURL( - path.resolve(__dirname, "../../../../server/dist/server/server/daemon-instance.js"), - ).href; - const { stopDaemonInstance } = (await import( - lifecycleUrl - )) as typeof import("@getpaseo/server/daemon-control"); - await stopDaemonInstance(paseoHome, { - force: true, - requestShutdown: async () => { - const client = await connectDaemonClient({ - port, - clientIdPrefix: "isolated-host-cleanup", - }); - try { - await client.shutdownServer(); - } finally { - await client.close(); - } - }, - }); + // The CLI owns graceful shutdown, forced cleanup and detached terminal + // reaping. Keep its ESM graph out of Playwright's transforming loader. + await execFileAsync( + process.execPath, + [ + path.resolve(__dirname, "../../../../cli/dist/index.js"), + "daemon", + "stop", + "--home", + paseoHome, + "--force", + ], + { timeout: 30_000 }, + ); await killProcessTree(child); }; const spawnDaemon = async (): Promise => {