diff --git a/packages/adapter-utils/src/server-utils.test.ts b/packages/adapter-utils/src/server-utils.test.ts index 131b3460210..4d338a0ff2f 100644 --- a/packages/adapter-utils/src/server-utils.test.ts +++ b/packages/adapter-utils/src/server-utils.test.ts @@ -467,6 +467,23 @@ describe("runChildProcess", () => { await expect(run).rejects.toThrow("database unavailable"); }); + it("rejects when a no-stdin child exits before onSpawn persistence fails", async () => { + const run = runChildProcess(randomUUID(), process.execPath, ["-e", ""], { + cwd: process.cwd(), + env: {}, + timeoutSec: 5, + graceSec: 1, + onLog: async () => {}, + onLogError: () => {}, + onSpawn: async () => { + await new Promise((resolve) => setTimeout(resolve, 100)); + throw new Error("database unavailable after exit"); + }, + }); + + await expect(run).rejects.toThrow("database unavailable after exit"); + }); + it.skipIf(process.platform === "win32")("kills descendant processes on timeout via the process group", async () => { let descendantPid: number | null = null; diff --git a/packages/adapter-utils/src/server-utils.ts b/packages/adapter-utils/src/server-utils.ts index e17491e1588..116074ae3f6 100644 --- a/packages/adapter-utils/src/server-utils.ts +++ b/packages/adapter-utils/src/server-utils.ts @@ -3182,16 +3182,20 @@ export async function runChildProcess( signalRunningProcess({ child, processGroupId }, "SIGKILL"); reject(err instanceof Error ? err : new Error(String(err))); }; + const spawnPersistSucceeded = spawnPersistPromise.then( + () => true, + (err) => { + handleSpawnPersistenceFailure(err); + return false; + }, + ); if (opts.stdin != null && stdin) { - void spawnPersistPromise - .then(() => { - if (child.killed || stdin.destroyed) return; - stdin.write(opts.stdin as string); - stdin.end(); - }) - .catch(handleSpawnPersistenceFailure); - } else { - void spawnPersistPromise.catch(handleSpawnPersistenceFailure); + void spawnPersistSucceeded.then((persisted) => { + if (!persisted) return; + if (child.killed || stdin.destroyed) return; + stdin.write(opts.stdin as string); + stdin.end(); + }); } child.on("error", (err: Error) => { @@ -3217,30 +3221,33 @@ export async function runChildProcess( clearTerminalCleanupTimers(); runningProcesses.delete(runId); void logChain.finally(() => { - void Promise.resolve() - .then(() => target.cleanup?.()) - .finally(() => { - resolve({ - exitCode: code, - signal, - timedOut, - stdout, - stderr, - pid: child.pid ?? null, - startedAt, - terminalResultCleanup: terminalCleanupStarted - ? { - kind: "terminal_result_cleanup", - stopped: true, - stopReason: UNMANAGED_BACKGROUND_TASK_STOP_REASON, - reason: UNMANAGED_BACKGROUND_TASK_LIVENESS_REASON, - terminalResultSeen, - signal: terminalCleanupSignal, - forceKilled: terminalCleanupForceKilled, - } - : null, - }); - }); + void spawnPersistSucceeded.then((persisted) => { + void Promise.resolve() + .then(() => target.cleanup?.()) + .finally(() => { + if (!persisted) return; + resolve({ + exitCode: code, + signal, + timedOut, + stdout, + stderr, + pid: child.pid ?? null, + startedAt, + terminalResultCleanup: terminalCleanupStarted + ? { + kind: "terminal_result_cleanup", + stopped: true, + stopReason: UNMANAGED_BACKGROUND_TASK_STOP_REASON, + reason: UNMANAGED_BACKGROUND_TASK_LIVENESS_REASON, + terminalResultSeen, + signal: terminalCleanupSignal, + forceKilled: terminalCleanupForceKilled, + } + : null, + }); + }); + }); }); }); })