fix(codemode): ignore Node --watch notifications from the worker

Node 24/26 --watch posts watch:import/watch:require messages on the
worker channel (nodejs/node#65044), which failed the run as a broken
bridge.

closes #10725
This commit is contained in:
Armin Ronacher 2026-10-10 19:13:04 +02:00
parent 4ac0bd8c7b
commit 4130bd5b32
4 changed files with 32 additions and 4 deletions

View file

@ -12,6 +12,10 @@
- The VM now polls for interrupts while it drains promise jobs, so a script such as `while (true) await null` is stopped at the same points as a synchronous loop
### Fixed
- Fixed `CodemodeSandbox` failing with a broken bridge error when the host runs under `node --watch` on Node 24 or 26, which posts `watch:import`/`watch:require` messages on the worker channel ([#10725](https://github.com/earendil-works/pi/issues/10725))
## [1.1.0] - 2026-10-07
### Added

View file

@ -35,7 +35,16 @@ function workerLauncher(
} catch (error) {
throw new Error(`Failed to start worker: ${errorMessage(error)}`);
}
worker.on("message", (message: unknown) => events.message(message));
worker.on("message", (message: unknown) => {
// Node 24/26 --watch leaks dependency reports onto the worker channel (nodejs/node#65044).
if (
typeof message === "object" &&
message !== null &&
("watch:import" in message || "watch:require" in message)
)
return;
events.message(message);
});
worker.on("error", (error: unknown) => {
events.failure({
kind: "sandbox",

View file

@ -1,7 +1,8 @@
/**
* Stands in for the real worker in host tests: posts the JSON message passed as the script source,
* so tests can send the host payloads the real prelude never produces.
* Stands in for the real worker in host tests: posts the JSON message (or array of messages) passed
* as the script source, so tests can send the host payloads the real prelude never produces.
*/
import { parentPort, workerData } from "node:worker_threads";
parentPort?.postMessage(JSON.parse((workerData as { code: string }).code));
const messages: unknown = JSON.parse((workerData as { code: string }).code);
for (const message of Array.isArray(messages) ? messages : [messages]) parentPort?.postMessage(message);

View file

@ -835,6 +835,20 @@ describe("worker host", () => {
});
});
// #10725: Node 24/26 `--watch` posts dependency reports on the worker channel.
it("ignores Node --watch notifications from the worker", async () => {
const sandbox = new CodemodeSandbox({ workerUrl: new URL("./fixtures/raw-worker.ts", import.meta.url) });
sandboxes.push(sandbox);
const result = await sandbox.execute(
JSON.stringify([
{ "watch:require": ["/worker.js"] },
{ "watch:import": ["file:///worker.js"] },
{ type: "done", ok: true, value: "42", writes: "[]" },
]),
);
expect(result).toMatchObject({ ok: true, value: 42 });
});
// #10444: a malformed payload from the worker must fail the run as a sandbox error instead of
// throwing in the host's message listener and never settling.
it.each([