Claude in Chrome bridge silently fails when both Desktop and CLI native-messaging hosts are registered for the same extension
Environment
- Claude Code CLI: 2.1.123 (also reproduces on 2.1.122)
- Claude Desktop: 1.5354.0 (installed but not used)
- Claude browser extension: 1.0.69 (ID:
fcoeoabgfenejglbffodgkkbkcdhcgfn) - Google Chrome: 147.0.7727.116
- macOS: 26.2 (build 25C56)
- Chrome profile in use: "Profile 1" (extension installed only there, not in Default)
Symptom
Every mcp__claude-in-chrome__* tool call from Claude Code CLI returns:
Browser extension is not connected. Please ensure the Claude browser extension is installed and running (https://claude.ai/chrome), and that you are logged into claude.ai with the same account as Claude Code...
The user is signed into claude.ai in Chrome with the same account as the CLI, the extension is installed and enabled, the sidepanel chat works, and Chrome was restarted multiple times. /chrome slash command shows "Status: Enabled" and "Extension: Installed". "Reconnect extension" does not fix it.
Root cause (architectural)
The extension's service worker iterates two native-messaging host names in fixed order, taking the first one that returns a pong:
const s = [
{ name: "com.anthropic.claude_browser_extension", label: "Desktop" },
{ name: "com.anthropic.claude_code_browser_extension", label: "Claude Code" }
];
for (const a of s) try {
const t = chrome.runtime.connectNative(a.name);
// ... ping/pong with 10s timeout, breaks on first success ...
}
(Source: Profile 1/Extensions/fcoeoabgfenejglbffodgkkbkcdhcgfn/1.0.69_0/assets/service-worker.ts-tDtlCDd6.js)
Both manifests ship with the same extension ID in allowed_origins:
~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.anthropic.claude_browser_extension.json→/Applications/Claude.app/Contents/Helpers/chrome-native-host(Rust binary, ships with Claude Desktop installer)~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.anthropic.claude_code_browser_extension.json→~/.claude/chrome/chrome-native-host(wrapper that execsclaude --chrome-native-host)
Because Claude Desktop is installed (even if never opened — the helper binary is just a Rust executable with no GUI dependency), the Desktop host wins the loop. Chrome spawns the Desktop helper, the helper opens its bridge socket at /tmp/claude-mcp-browser-bridge-$USER/<pid>.sock, the extension marks "connected" — but the CLI's claude-in-chrome MCP client cannot complete the upstream handshake against the Desktop helper, so every tool call fails with "Browser extension is not connected."
Evidence
lsofconfirmed bridge socket owned by/Applications/Claude.app/Contents/Helpers/chrome-native-host, parent = Chrome.- Quitting the Claude Desktop GUI does NOT help — Chrome relaunches the Desktop helper binary on demand because the manifest is what's registered, not the app.
- Socket directory permissions/ownership pass the CLI's
validateSocketSecuritycheck (0700 dir, 0600 socket, user-owned). - Both fresh and existing CLI sessions (v2.1.122 and v2.1.123) fail identically.
Workaround that proves the diagnosis (partially)
mv ~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/com.anthropic.claude_browser_extension.json \
~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/com.anthropic.claude_browser_extension.json.disabled
Then toggle the extension off/on in chrome://extensions/.
After this, Chrome correctly spawns claude --chrome-native-host as the helper (ps -ef confirms). The CLI's helper Unix socket is created, stdio pipes to Chrome are alive, and the helper has been observed running for 4+ minutes.
Remaining issue (after workaround)
Even with the CLI helper as the bridge, mcp__claude-in-chrome__* calls still fail with the same error.
The CLI helper does implement case "ping": ... type:"pong" ... and case "get_status": ... type:"status_response", and emits {type:"mcp_connected"} on stdout when an MCP client connects to its Unix socket. But the extension's mcpConnected flag is not getting set. The extension's service worker is heavily minified with console.log calls stripped, so we couldn't get a live error from the SW DevTools console to pin the exact failure.
Plausible secondary causes:
- Schema mismatch: helper does
YJ3().safeParse(_)(Zod strict) on incoming Chrome messages — if the schema rejects{type:"ping"}(e.g. requires extra fields), helper sends{type:"error"}instead of{type:"pong"}and the extension's pong-listener (matching onlyr.type === "pong") times out. - Manifest V3 service-worker idle race around the post-
get_statusmcp_connectednotification.
Suggested fixes
- Short-term: in the extension's host iteration, don't trust
connectNative+ ping/pong as proof of bridge health — also wait for the upstreammcp_connectednotification (with a short timeout) before committing to a host. If it doesn't arrive, fall through to the next host name. - Longer-term: have the Claude Desktop installer either skip the chrome native-messaging manifest when the user opted into Claude Code, or make the Desktop helper detect "no Desktop GUI auth available" and refuse the
pongso the loop falls through. - Diagnostic: leave at least one
console.errorin the extension's bridge handshake so users can see the failure mode.
Reproduction
- Install Claude Desktop (
/Applications/Claude.app/) — but never use it. - Install Claude Code CLI and the Chrome extension; sign into claude.ai with the matching account.
- From CLI, run any
mcp__claude-in-chrome__*tool. Observe "Browser extension is not connected." - Verify dual manifest registration:
ls ~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/com.anthropic.claude*.
This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗