Claude in Chrome bridge silently fails when both Desktop and CLI native-messaging hosts are registered for the same extension

Resolved 💬 6 comments Opened Apr 29, 2026 by sahasra098 Closed Jun 5, 2026

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 execs claude --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

  • lsof confirmed 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 validateSocketSecurity check (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:

  1. 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 only r.type === "pong") times out.
  2. Manifest V3 service-worker idle race around the post-get_status mcp_connected notification.

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 upstream mcp_connected notification (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 pong so the loop falls through.
  • Diagnostic: leave at least one console.error in the extension's bridge handshake so users can see the failure mode.

Reproduction

  1. Install Claude Desktop (/Applications/Claude.app/) — but never use it.
  2. Install Claude Code CLI and the Chrome extension; sign into claude.ai with the matching account.
  3. From CLI, run any mcp__claude-in-chrome__* tool. Observe "Browser extension is not connected."
  4. Verify dual manifest registration: ls ~/Library/Application\ Support/Google/Chrome/NativeMessagingHosts/com.anthropic.claude*.

View original on GitHub ↗

This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗