Skip to content

Windows: native rlm_query tool fails with spawn pi ENOENT (npm .cmd shim not spawnable) + os field blocks install #22

Description

@ukind

Summary

On Windows, the native rlm_query tool fails to spawn child Pi processes with spawn pi ENOENT. The child never starts; the calling agent sees the raw ENOENT error.

Environment

  • OS: Windows 11 (win32)
  • Node: v24.14.0 (also reproduced shape of the issue on any modern Node ≥ 18.20/20.12, where spawning .cmd/.bat without shell: true is impossible by design)
  • pi: 0.85.1 (pi -p headless, installed via npm global → pi.cmd shim)
  • pi-recursive: 0.6.1 (npm)

Reproduction

  1. On Windows, install the extension (requires bypassing the os field — see related note below).
  2. In any Pi session, ask: Use the native rlm_query tool to ask a child agent what 12 * 7 is.
  3. The tool call returns a child error containing:
Error: spawn pi ENOENT

The calling agent can still work around it by manually spawning pi -p, but the native tool path is dead.

Root cause

extensions/ypi/native-tool.tsspawnChildPi():

const child = spawn(process.env.YPI_PI_BIN || "pi", args, { ... });

On Windows, npm global installs create pi.cmd shims, not executables. Node's spawn() cannot execute .cmd shims without shell: true (and since the CVE-2024-27980 fix, spawning .cmd/.bat without shell: true is rejected outright). So spawn("pi", ...)ENOENT.

Two adjacent observations:

  • detached: process.platform !== "win32" shows the code already special-cases win32, but the bin resolution was missed.
  • The registration block already debug-warns when YPI_PI_BIN is not executable (canExecute), so bin-resolution problems were anticipated — but the default pi path itself is broken on win32.

Why not shell: true

The obvious one-liner (shell: process.platform === "win32") is unsafe here: args ends with args.push(params.prompt) — a multi-line, arbitrary-text prompt passed as a spawn argument. With shell: true, Node concatenates args unquoted into a cmd.exe command line: quotes, &, ^, %VAR%, and newlines in the prompt would break or inject into the shell command. Multi-line args cannot be carried through cmd.exe at all.

Tested fix

On win32, spawn process.execPath (node/bun) with process.argv[1] (Pi's own entry script) prepended to the args. No shell involved, no quoting hazards, and it inherits whatever runtime launched the parent (node or bun). Verified working on Windows: rlm_query → child Pi → correct answer, no errors.

function spawnChildPi(args: string[], env: NodeJS.ProcessEnv, cwd: string, timeoutSeconds: number | undefined, signal?: AbortSignal): Promise<NativeRunResult> {
	return new Promise((resolve, reject) => {
		// win32: npm's `pi` is a .cmd shim — spawn("pi") throws ENOENT (and .cmd
		// requires shell:true since CVE-2024-27980). shell:true is unsafe here because
		// args carries the multi-line prompt. Spawn the parent's own runtime + entry instead.
		const useNodeEntry = process.platform === "win32" && !process.env.YPI_PI_BIN && !!process.argv[1];
		const bin = useNodeEntry ? process.execPath : (process.env.YPI_PI_BIN || "pi");
		const childArgs = useNodeEntry ? [process.argv[1], ...args] : args;
		const child = spawn(bin, childArgs, {
			cwd,
			env,
			stdio: ["ignore", "pipe", "pipe"],
			detached: process.platform !== "win32",
		});
		// ... unchanged

YPI_PI_BIN keeps priority for users who explicitly set it.

Related note: os field blocks Windows entirely

package.json restricts the package to "os": ["linux", "darwin"], so both documented install paths fail on win32:

  • pi install npm:pi-recursive → npm notsup (the install above only works with --force)
  • pi -e npm:pi-recursive → Pi runs its own npm install (no --force) into a temp dir → same notsup failure, so the one-shot mode documented in the README can never work on Windows

The extension itself is pure TypeScript and runs fine on win32 once installed (the only real blocker was the spawn bug above). If Windows is meant to be supported, dropping the os restriction (or adding "win32") plus the spawn fix would make both paths work; if not, a README note would save Windows users the debugging session.

Happy to turn the patch into a PR if useful.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions