can1357/oh-my-pi · error

Windows batch files require application "cmd.exe" with the b

Error message

Windows batch files require application "cmd.exe" with the batch path after "/c"

What it means

When starting a PTY daemon on Windows, DaemonBroker refuses specs whose application is a .bat or .cmd batch file invoked directly: Windows cannot exec batch scripts as images, so they must run through cmd.exe with the batch path supplied after the /c flag. The error enforces that invocation shape.

Source

Thrown at packages/coding-agent/src/launch/broker.ts:608

			}
			case "shutdown":
				return { op: "shutdown" };
		}
	}

	async #start(spec: DaemonSpec, owner?: string): Promise<DaemonRpcResult> {
		if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,47}$/.test(spec.name)) {
			throw new Error("Daemon name must be 1-48 letters, numbers, dots, underscores, or hyphens");
		}
		if (spec.detached && spec.pty) {
			throw new Error("A detached daemon cannot allocate a PTY");
		}
		if (
			spec.pty &&
			process.platform === "win32" &&
			[".bat", ".cmd"].includes(path.extname(spec.application).toLowerCase())
		) {
			throw new Error('Windows batch files require application "cmd.exe" with the batch path after "/c"');
		}
		if (this.#startingNames.has(spec.name)) {
			throw new Error(`Daemon ${spec.name} is already starting`);
		}
		this.#startingNames.add(spec.name);
		let record: ManagedDaemon;
		try {
			const existing = this.#records.get(spec.name);
			if (existing) await this.#refreshDetached(existing);
			if (existing && !terminalState(existing.snapshot.state)) {
				throw new Error(`Daemon ${spec.name} is already ${existing.snapshot.state}`);
			}
			if (existing && existing.pendingCompletions.length > 0) {
				throw new Error(`Daemon ${spec.name} has unacknowledged completion notifications`);
			}
			if (spec.ready?.log) {
				try {
					new RegExp(spec.ready.log, "u");

View on GitHub (pinned to 9690622007)

Solutions

  1. Set spec.application to 'cmd.exe' and pass the batch file as the first argument after '/c' (e.g. args: ['/c', 'C:\\scripts\\build.bat', ...])
  2. Use the underlying executable directly (e.g. node.exe script.js) instead of its .cmd shim when possible
  3. Guard the spec builder to apply the cmd.exe wrapping only on win32 when a PTY is requested

Example fix

// before
await broker.start({ name: 'build', pty: true, application: 'C:\\scripts\\build.bat' }); // throws on Windows
// after
await broker.start({ name: 'build', pty: true, application: 'cmd.exe', args: ['/c', 'C:\\scripts\\build.bat'] });
Defensive patterns

Strategy: validation

Validate before calling

function toPtyApplication(spec: DaemonSpec): DaemonSpec {
  if (process.platform !== 'win32' || !spec.pty) return spec;
  const ext = spec.application.slice(spec.application.lastIndexOf('.')).toLowerCase();
  if (ext === '.bat' || ext === '.cmd') {
    return { ...spec, application: 'cmd.exe', args: ['/c', spec.application, ...(spec.args ?? [])] };
  }
  return spec;
}
// pass through toPtyApplication(spec) before start

Try / catch

try {
  await broker.start(spec);
} catch (err) {
  if (err instanceof Error && err.message.includes('Windows batch files require application')) {
    return broker.start({ ...spec, application: 'cmd.exe', args: ['/c', spec.application, ...(spec.args ?? [])] });
  }
  throw err;
}

Prevention

When it happens

Trigger: spec.pty is true, process.platform is 'win32', and path.extname(spec.application) is .bat or .cmd — e.g. application: 'C:\\scripts\\build.bat' or 'run.cmd' passed directly to the start RPC.

Common situations: Config written on macOS/Linux where direct batch paths never arise, then reused on Windows; npm .cmd shims (npm.cmd, tsc.cmd) referenced directly; installers generating batch wrappers whose path is fed as the daemon application.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/1201f990e9033433. Report an issue: GitHub.