diff --git a/backend/src/pi/pi-process-manager.ts b/backend/src/pi/pi-process-manager.ts index c9701d58..5c8a8877 100644 --- a/backend/src/pi/pi-process-manager.ts +++ b/backend/src/pi/pi-process-manager.ts @@ -30,7 +30,10 @@ export class PiProcessManager { THT_AUTHOR: author, PATH: `${harnessVenvBin}:${process.env.PATH ?? ""}`, }; - const child = nodeSpawn(cfg.piBin, ["--mode", "rpc", "--approve"], { + // pi 0.73 (the @mariozechner rebrand) removed the `--approve` flag: rpc mode is + // headless and runs tools without an approval gate, so passing it makes pi exit + // with "Unknown option: --approve". Args are intentionally just `--mode rpc`. + const child = nodeSpawn(cfg.piBin, ["--mode", "rpc"], { cwd: cfg.harnessDir, env, }); diff --git a/backend/test/pi-spawn-args.test.ts b/backend/test/pi-spawn-args.test.ts new file mode 100644 index 00000000..2488f09d --- /dev/null +++ b/backend/test/pi-spawn-args.test.ts @@ -0,0 +1,34 @@ +import { test, expect, vi } from "vitest"; +import { EventEmitter } from "node:events"; + +// Mock node:child_process so the PRODUCTION spawnFn (no injected spawnFn) is exercised +// without launching a real pi. Per-file mock — kept out of pi-process-manager.test.ts, +// which deliberately spawns the real fake_pi. +vi.mock("node:child_process", () => ({ + spawn: vi.fn(() => { + const ch: any = new EventEmitter(); + ch.stdout = new EventEmitter(); + ch.stderr = new EventEmitter(); + ch.stderr.resume = () => {}; + ch.stdin = { write: () => true }; + ch.kill = () => {}; + return ch; + }), +})); +import { spawn as nodeSpawn } from "node:child_process"; +import { PiProcessManager } from "../src/pi/pi-process-manager.js"; +import { loadConfig } from "../src/config.js"; + +test("production spawnFn launches `pi --mode rpc` with no --approve (pi 0.73 dropped it)", async () => { + (nodeSpawn as any).mockClear(); + // loadConfig({}) leaves provider/model/thinking unset → spawnFor skips the rpc + // handshake and just spawns + sends the prompt, so the mocked child never hangs. + const cfg = loadConfig({}); + const mgr = new PiProcessManager(cfg); // NO injected spawnFn → uses the production spawnFn + await mgr.spawnFor("s1", {}); + const [bin, args] = (nodeSpawn as any).mock.calls[0]; + expect(bin).toBe(cfg.piBin); + expect(args).toEqual(["--mode", "rpc"]); + expect(args).not.toContain("--approve"); + mgr.teardown("s1"); +});