can1357/oh-my-pi · error · ToolError

app.path must be absolute (got ${JSON.stringify(exe)}). Pass

Error message

app.path must be absolute (got ${JSON.stringify(exe)}). Pass the binary inside Foo.app/Contents/MacOS/, not the .app bundle.

What it means

When opening a local application browser (a real browser binary, e.g. a Chrome.app), openBrowserHandle requires kind.path to be an absolute path to the executable binary inside the bundle. It throws ToolError when a relative path — typically the .app bundle directory itself — is supplied, because reusing/attaching logic (findReusableCdp) and process management need the exact executable.

Source

Thrown at packages/coding-agent/src/tools/browser/registry.ts:258

		const puppeteer = await loadPuppeteer();
		const browser = await puppeteer.connect({
			browserURL: cdpUrl,
			defaultViewport: null,
			protocolTimeout: BROWSER_PROTOCOL_TIMEOUT_MS,
		});
		return {
			key: browserKey(kind),
			kind,
			browser,
			cdpUrl,
			refCount: 0,
			stealth: { browserSession: null, override: null },
		};
	}

	const exe = kind.path;
	if (!path.isAbsolute(exe)) {
		throw new ToolError(
			`app.path must be absolute (got ${JSON.stringify(exe)}). Pass the binary inside Foo.app/Contents/MacOS/, not the .app bundle.`,
		);
	}
	const reused = await findReusableCdp(exe, opts.signal);
	let cdpUrl: string;
	let pid: number;
	let subprocess: Subprocess | undefined;
	if (reused) {
		logger.debug("Reusing existing CDP endpoint for attach", { exe, pid: reused.pid, cdpUrl: reused.cdpUrl });
		cdpUrl = reused.cdpUrl;
		pid = reused.pid;
	} else {
		const killed = await killExistingByPath(exe, opts.signal);
		if (killed > 0) logger.debug("Killed existing instances before attach", { exe, killed });
		const port = await findFreeCdpPort();
		const launchArgs = [...(opts.appArgs ?? []), `--remote-debugging-port=${port}`];
		const child = Bun.spawn([exe, ...launchArgs], {
			stdout: "ignore",

View on GitHub (pinned to 9690622007)

Solutions

  1. Set app.path to the absolute binary path inside the bundle, e.g. /Applications/Google Chrome.app/Contents/MacOS/Google Chrome (or Chromium/Electron equivalent)
  2. On Linux/Windows use the absolute executable path, e.g. /usr/bin/chromium or C:\\Program Files\\...\\chrome.exe
  3. Resolve the binary programmatically if the path may be relative: path.resolve(exe) before configuring

Example fix

// before
{ "path": "/Applications/Chromium.app" }
// after
{ "path": "/Applications/Chromium.app/Contents/MacOS/Chromium" }
Defensive patterns

Strategy: validation

Validate before calling

import * as path from "node:path";
function assertAbsoluteExe(exe: string): string {
  if (!path.isAbsolute(exe)) throw new Error(`app.path must be absolute: ${exe}`);
  if (exe.endsWith(".app")) throw new Error(`app.path must be the binary inside ${exe}/Contents/MacOS/, not the .app bundle`);
  return exe;
}

Type guard

function isAbsoluteBinaryPath(p: string): boolean {
  return path.isAbsolute(p) && !p.endsWith(".app");
}

Prevention

When it happens

Trigger: Configuring a browser app with path like "/Applications/Google Chrome.app" (the bundle) or a relative path like "chrome" instead of "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome".

Common situations: macOS users pointing at the .app bundle; PATH-style executable names relying on shell lookup; hand-edited config files with abbreviated paths.

Related errors


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