Summary
On macOS, chrome-process.js (the code path used by the MCP use_browser tool) always launches the literal /Applications/Google Chrome.app binary — the same bundle the user's everyday browser uses. When automation Chrome instances pile up (see below), macOS's per-bundle "app is running" tracking gets hijacked: clicking the Dock icon / Spotlight-launching "Google Chrome" just activates one of the tool's headless/windowless automation instances instead of opening a real window. To the user this looks exactly like "my browser won't start."
Root cause, in two parts
1. CHROME_WS_BROWSER is documented but not honored in the MCP path.
README.md / COMMANDLINE-USAGE.md document CHROME_WS_BROWSER as overriding auto-detection, and the standalone chrome-ws CLI does read it (chrome-ws:316):
const chromePath = process.env.CHROME_WS_BROWSER || paths.find(p => existsSync(p));
But skills/browsing/lib/chrome-process.js — which is what mcp/dist/index.js actually requires at runtime (require(join(__dirname, "../../skills/browsing/chrome-ws-lib.js")) → chrome-process.js) — never reads it at all. The env var silently does nothing for anyone using the MCP tool (as opposed to the CLI).
2. No way to point automation at a separate Chrome bundle, so it collides with the daily browser.
Because the hardcoded darwin path list only ever contains the main Google Chrome.app, every automation instance (any of superpowers-chrome, -2, -3, ... profiles) shares bundle id com.google.Chrome with the user's regular browser. Combined with instances that never get cleaned up across crashed/closed Claude Code sessions (I found 6 live automation Chrome processes across 4 different profile dirs, one running since 3 days prior, plus 4 separate MCP server node processes going back a week — all still holding old in-memory code via require() caching), the result is that macOS Launch Services considers "Google Chrome" perpetually running, and normal launch attempts just re-activate a headless/invisible instance.
Suggested fix
Minimal patch to skills/browsing/lib/chrome-process.js — honor CHROME_WS_BROWSER (bringing it in line with the CLI and the docs), and prefer a dedicated automation-only Chrome build when present so day-to-day Chrome is never touched by default:
// --- Step 3: Find Chrome binary ---
const chromePaths = {
darwin: [
+ `${os.homedir()}/Library/Caches/superpowers/chrome-for-testing/current`,
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'/Applications/Chromium.app/Contents/MacOS/Chromium'
],
...
};
const platform = os.platform();
const paths = chromePaths[platform] || [];
- let chromePath = null;
- for (const path of paths) {
- if (existsSync(path)) {
- chromePath = path;
- break;
- }
+ let chromePath = process.env.CHROME_WS_BROWSER;
+ if (chromePath && !existsSync(chromePath)) chromePath = null;
+ if (!chromePath) {
+ for (const path of paths) {
+ if (existsSync(path)) {
+ chromePath = path;
+ break;
+ }
+ }
}
(chrome-for-testing/current being a symlink the user/installer points at a @puppeteer/browsers-installed "Google Chrome for Testing.app", which has its own distinct bundle id and therefore never collides with the daily browser's running-app state.)
I've verified this resolution logic in isolation — it correctly prefers the dedicated build when present and falls back to the shared Google Chrome.app otherwise, with no behavior change for anyone who hasn't installed a separate build.
Secondary issue: process leakage across sessions
Separately (and probably deserving its own issue if useful): I found MCP server node processes and their spawned Chrome instances still alive from sessions started 3-7 days earlier, all under different auto-incremented profile names (superpowers-chrome, -2, -3, -4...). Nothing appears to reap these when a Claude Code session ends unexpectedly (vs. a clean chrome-ws kill/shutdown). Over time this silently accumulates background Chrome processes and — per the above — increasingly masks the user's real browser. Happy to file separately if that's preferred.
Environment
- superpowers-chrome 3.0.2 (same code present on
main as of this writing)
- macOS, Apple Silicon
Summary
On macOS,
chrome-process.js(the code path used by the MCPuse_browsertool) always launches the literal/Applications/Google Chrome.appbinary — the same bundle the user's everyday browser uses. When automation Chrome instances pile up (see below), macOS's per-bundle "app is running" tracking gets hijacked: clicking the Dock icon / Spotlight-launching "Google Chrome" just activates one of the tool's headless/windowless automation instances instead of opening a real window. To the user this looks exactly like "my browser won't start."Root cause, in two parts
1.
CHROME_WS_BROWSERis documented but not honored in the MCP path.README.md/COMMANDLINE-USAGE.mddocumentCHROME_WS_BROWSERas overriding auto-detection, and the standalonechrome-wsCLI does read it (chrome-ws:316):But
skills/browsing/lib/chrome-process.js— which is whatmcp/dist/index.jsactually requires at runtime (require(join(__dirname, "../../skills/browsing/chrome-ws-lib.js"))→chrome-process.js) — never reads it at all. The env var silently does nothing for anyone using the MCP tool (as opposed to the CLI).2. No way to point automation at a separate Chrome bundle, so it collides with the daily browser.
Because the hardcoded
darwinpath list only ever contains the mainGoogle Chrome.app, every automation instance (any ofsuperpowers-chrome,-2,-3, ... profiles) shares bundle idcom.google.Chromewith the user's regular browser. Combined with instances that never get cleaned up across crashed/closed Claude Code sessions (I found 6 live automation Chrome processes across 4 different profile dirs, one running since 3 days prior, plus 4 separate MCP server node processes going back a week — all still holding old in-memory code viarequire()caching), the result is that macOS Launch Services considers "Google Chrome" perpetually running, and normal launch attempts just re-activate a headless/invisible instance.Suggested fix
Minimal patch to
skills/browsing/lib/chrome-process.js— honorCHROME_WS_BROWSER(bringing it in line with the CLI and the docs), and prefer a dedicated automation-only Chrome build when present so day-to-day Chrome is never touched by default:// --- Step 3: Find Chrome binary --- const chromePaths = { darwin: [ + `${os.homedir()}/Library/Caches/superpowers/chrome-for-testing/current`, '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', '/Applications/Chromium.app/Contents/MacOS/Chromium' ], ... }; const platform = os.platform(); const paths = chromePaths[platform] || []; - let chromePath = null; - for (const path of paths) { - if (existsSync(path)) { - chromePath = path; - break; - } + let chromePath = process.env.CHROME_WS_BROWSER; + if (chromePath && !existsSync(chromePath)) chromePath = null; + if (!chromePath) { + for (const path of paths) { + if (existsSync(path)) { + chromePath = path; + break; + } + } }(
chrome-for-testing/currentbeing a symlink the user/installer points at a@puppeteer/browsers-installed "Google Chrome for Testing.app", which has its own distinct bundle id and therefore never collides with the daily browser's running-app state.)I've verified this resolution logic in isolation — it correctly prefers the dedicated build when present and falls back to the shared
Google Chrome.appotherwise, with no behavior change for anyone who hasn't installed a separate build.Secondary issue: process leakage across sessions
Separately (and probably deserving its own issue if useful): I found MCP server node processes and their spawned Chrome instances still alive from sessions started 3-7 days earlier, all under different auto-incremented profile names (
superpowers-chrome,-2,-3,-4...). Nothing appears to reap these when a Claude Code session ends unexpectedly (vs. a cleanchrome-ws kill/shutdown). Over time this silently accumulates background Chrome processes and — per the above — increasingly masks the user's real browser. Happy to file separately if that's preferred.Environment
mainas of this writing)