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
- 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)
- On Linux/Windows use the absolute executable path, e.g. /usr/bin/chromium or C:\\Program Files\\...\\chrome.exe
- 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
- Always configure the executable inside Foo.app/Contents/MacOS/, never the .app bundle itself
- Use absolute paths; never rely on PATH lookup for browser binaries
- Sanity-check the configured path exists (fs access) before saving the config
- On macOS, discover binaries with `mdfind` or the app bundle layout, not `which`
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
- browser app.cdp_url must be the HTTP CDP discovery endpoint
- OMP_AUTH_BROKER_ACCOUNT_POOL_FILE contains an empty provider
- OMP_AUTH_BROKER_ACCOUNT_POOL_FILE contains a provider id wit
- ${name} path does not exist: ${trimmed}
- Anthropic thinking budget requires max_tokens greater than $
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/a2db0c4b5caa2a2b.
Report an issue: GitHub.