diff --git a/packages/coding-agent/src/eval/__tests__/js-context-manager.test.ts b/packages/coding-agent/src/eval/__tests__/js-context-manager.test.ts index 08a2d9aa8..8cd2941e8 100644 --- a/packages/coding-agent/src/eval/__tests__/js-context-manager.test.ts +++ b/packages/coding-agent/src/eval/__tests__/js-context-manager.test.ts @@ -280,6 +280,35 @@ describe("JavaScript eval worker lifecycle", () => { expect(stats.terminateCalls).toBe(1); }); + it("falls back to a Bun Worker when the subprocess cannot spawn", async () => { + using tempDir = TempDir.createSync("@omp-js-spawn-fallback-"); + // Exercise the production ladder (process -> worker -> inline), not the + // worker-thread test seam the surrounding describe enables. + setJsEvalWorkerThreadForTests(false); + const stats: FakeWorkerStats = { closeRequests: 0, terminateCalls: 0 }; + installFakeWorker(stats, { exitOnClose: true, settleRuns: true }); + const originalSpawn = Bun.spawn; + let spawnAttempts = 0; + Bun.spawn = ((): never => { + spawnAttempts++; + throw new Error("subprocess spawn unavailable"); + }) as unknown as typeof Bun.spawn; + + try { + const session = makeSession(tempDir.path()); + const sessionId = `js-spawn-fallback:${crypto.randomUUID()}`; + // The fake Worker settles runs without executing the cell, so an empty + // output proves the middle rung handled it — the inline fallback would + // have actually evaluated the expression and printed 42. + const result = await executeJs("return String(6 * 7);", { cwd: tempDir.path(), sessionId, session }); + expect(result.exitCode).toBe(0); + expect(result.output.trim()).toBe(""); + expect(spawnAttempts).toBe(1); + } finally { + Bun.spawn = originalSpawn; + } + }); + it("falls back to the inline worker when the spawned worker errors during startup", async () => { using tempDir = TempDir.createSync("@omp-js-worker-error-"); const stats: FakeWorkerStats = { closeRequests: 0, terminateCalls: 0 }; diff --git a/packages/coding-agent/src/eval/js/context-manager.ts b/packages/coding-agent/src/eval/js/context-manager.ts index 536b01a5d..fc3bd7cbb 100644 --- a/packages/coding-agent/src/eval/js/context-manager.ts +++ b/packages/coding-agent/src/eval/js/context-manager.ts @@ -499,10 +499,12 @@ function spawnJsWorker(): WorkerHandle { try { return spawnJsProcess(); } catch (err) { - logger.warn("JS eval subprocess spawn failed; using inline JS eval worker (no sync-loop guard)", { + // Fall through to the Bun Worker rung: a worker thread still interrupts + // synchronous infinite loops via terminate(), which the inline fallback + // cannot. + logger.warn("JS eval subprocess spawn failed; falling back to a Bun Worker", { error: err instanceof Error ? err.message : String(err), }); - return spawnInlineWorker(); } } try {