Claude in Chrome: extension binds native host successfully but CLI always reports "Browser extension is not connected"
Bug: Claude in Chrome — extension binds native host but CLI reports "extension is not connected"
Environment
- macOS 25.6.0 (Darwin), Chrome 151.0.7922.77
- Claude Code 2.1.226 (
~/.local/bin/claude->~/.direnvdata/claude/versions/2.1.226) - Extension "Claude" v1.0.85, ID
fcoeoabgfenejglbffodgkkbkcdhcgfn, ChromeDefaultprofile - Claude desktop app installed at
/Applications/Claude.app(not running)
Symptom
Every mcp__claude-in-chrome__* call returns:
Browser extension is not connected. Please ensure the Claude browser extension is installed and running...
Reproduces in an existing session AND in a fresh claude --chrome -p process.
Verified healthy (ruled out)
- Extension enabled in
Default/Secure Preferences, nodisable_reasons. - Chrome running
Defaultprofile (last_used: Default), which is where the extension lives. - Account matches on both sides: CLI
oauthAccount.emailAddress=<user-email-A>;
claude.ai in-browser and extension settings both show the same address.
(Chrome's Google profile is a different address, <user-email-B> — irrelevant, ruled out.)
- Native messaging manifest
com.anthropic.claude_code_browser_extension.jsonpresent,
allowed_origins contains the extension ID, path -> ~/.claude/chrome/chrome-native-host
-> .../versions/2.1.226 --chrome-native-host (binary exists, executable, no quarantine xattr).
- Native host process spawns on extension connect, parent PID = Chrome, fds 0/1 are live pipes.
- Bridge dir
/tmp/claude-mcp-browser-bridge-<user>/contains exactly one socket, always matching a
live host PID. No stale sockets at any point.
- CLI config:
claudeInChromeDefaultEnabled: true,hasCompletedClaudeInChromeOnboarding: true,
cachedChromeExtensionInstalled: true.
- Manual
connect()to the bridge socket succeeds (accepts the connection).
Host selection logic (from extension service worker)
assets/service-worker.ts-Qx93VBt4.js probes hosts in fixed order, first ping/pong wins:
const t = [
{name:"com.anthropic.claude_browser_extension", label:"Desktop"},
{name:"com.anthropic.claude_code_browser_extension", label:"Claude Code"}
];
Result is cached in a sticky flag (if (ie) return !1) until the service worker restarts.
Observed consequence: with the desktop app's manifest registered, the extension bindsDesktop (/Applications/Claude.app/Contents/Helpers/chrome-native-host) and never tries the
Claude Code host — Claude Code can never obtain a browser while Claude.app is installed.
Both host bindings fail identically
| Config | Host bound | PID | Result |
|---|---|---|---|
| Desktop manifest registered | Desktop (Claude.app) | 15062 | not connected |
| Desktop manifest renamed .disabled | Claude Code (2.1.226 --chrome-native-host) | 20120 | not connected |
Extension service worker restarted (toggle off/on at chrome://extensions) after each change to
clear the sticky probe cache; host process + fresh socket confirmed respawned each time.
Open question
Extension opens the port and the host process starts with live pipes, but no session ever
registers with the CLI. Suspect the ping/pong handshake or post-handshake session registration.
Next diagnostic: chrome://extensions -> Claude -> "service worker" -> Console, for errors at
probe time. Native host stderr is redirected to /dev/null (fd 2 -> /dev/null), so host-side
failures are currently unobservable.
5 Comments
Still reproducible on v2.1.233 (Arch Linux, Brave 1.85, extension current from the Chrome Web Store).
Symptom matches this issue exactly: the extension↔native-host link is healthy, but the CLI's MCP tools permanently report "Browser extension is not connected".
Diagnostics from my session:
~/.config/BraveSoftware/Brave-Browser/NativeMessagingHosts/com.anthropic.claude_code_browser_extension.jsonexists and points at the profile'schrome-native-hostwrapper;allowed_originsmatches the store extension ID..../claude/versions/2.1.233 --chrome-native-host.claude --continue --chrome), and restarting Brave so wrapper/host/CLI all aligned on 2.1.233, the result is unchanged./chromecompletes without output;tabs_context_mcpstill returns "Browser extension is not connected" every time.So on Linux+Brave the failing hop appears to be session↔host discovery, not extension↔host. Happy to gather logs/strace from the host process if useful.
Reproduced on macOS Tahoe (Darwin 25.6.0), arm64, Chrome current stable, Claude Code CLI 2.1.239, extension installed via Chrome Web Store (ID
fcoeoabgfenejglbffodgkkbkcdhcgfn). Symptom matches this issue:/chromeshowsStatus: Enabled,Extension: Installed, but also throws a secondary error —Couldn't list connected browsers: JSON Parse error: Unexpected identifier "Browser"(looks like the CLI is trying toJSON.parse()the literal "Browser extension is not connected..." error string).tabs_context_mcpand friends always return "Browser extension is not connected."Went further than the extension↔host handshake this time — checked whether Chrome ever launches the native host at all:
~/.claude/chrome/chrome-native-hostwrapper, andcom.anthropic.claude_code_browser_extension.jsonmanifest (allowed_originscorrectly lists the installed extension ID) both check out and work fine when the wrapper is executed manually (echo '{"type":"ping"}' | ~/.claude/chrome/chrome-native-hostimmediately logs a clean startup + socket listener).~/.direnvdata/claude/versions/2.1.235) from a previous auto-update, while the active CLI had since moved to2.1.239— same class of bug as the Homebrew-Caskroom-path issue described in #84814. Fixed by pointing the wrapper at the stableclaudesymlink instead of a version-pinned path. Did not resolve the connection.~/Library/Logs/Claude/chrome-native-host.logshows zero native-host launches since 2026-08-08, despite: multiple full Chrome restarts (Cmd+Q, not just window close), repeatedly toggling the extension off/on inchrome://extensions, clicking "Reconnect extension" from/chrome(which opens a tab that lands onchrome://extensionsand does nothing else — no pairing/handshake page), and a complete extension removal + fresh reinstall + re-auth (confirmed same claude.ai account as the CLI, no separate API-key auth).profiles listempty), noNativeMessagingBlocklist/related keys in~/Library/Preferences/com.google.Chrome.plistor/Library/Managed Preferences/com.google.Chrome.plist.chrome://extensions→ Inspect views: service worker) shows no log output at all — not even after clicking "Reconnect extension" in/chromewhile watching it live.Conclusion: on this machine the failing hop is upstream of the native-host process itself — the extension's background service worker never appears to attempt
chrome.runtime.connectNative(), even though its own popup shows the account as connected/authorized. Manifest, wrapper, and host binary are all provably functional in isolation; Chrome (or the extension) just never invokes the native-messaging channel.Found the root cause on my end (macOS, arm64, Chrome stable) — not a bug in the extension/native-host handshake itself, but a self-inflicted environment conflict: I had a
launchdLaunchAgent keeping an always-on, automated Chrome instance running in the background for an unrelated browser-automation workflow:It launched the same Google Chrome application (not a separate binary) with:
plus both
RunAtLoad=trueandKeepAlive=true, so it started at login and immediately relaunched itself if killed.Even with a separate
--user-data-dir, it's still the same Chrome application/process identity as my normal browsing session, and the Claude extension had synced into both profiles. In practice:KeepAliveimmediately respawned the automation instance and won the race again.Fix: fully removed the automation LaunchAgent (unload + delete the plist), deleted the dedicated profile dir, confirmed port 9222 was no longer bound, and removed the tooling that spun it up. Once there was only one Chrome process/profile identity on the machine, the extension paired correctly and
/chrome+tabs_context_mcpstarted working immediately — no CLI/extension reinstall needed.For anyone else still hitting this: worth checking for any long-lived, auto-restarting browser process on your machine — a
launchd/systemdservice, cron job, Docker container, or agent framework that launches Chrome/Brave/Chromium with--remote-debugging-portand an auto-restart policy (KeepAlive,Restart=always, etc.), especially under a separate--user-data-dir. If that process wins the race to "be Chrome" before your interactive browser session does, the extension can end up paired to the wrong instance while looking completely healthy from the popup.Closing this out on my end since it's resolved — reopen or ping if others confirm the same root cause.
@quinnjr — flagging a possible lead, though your setup (Arch Linux + Brave) is different enough from mine (macOS + Chrome) that I can't promise it's the same root cause. Mine turned out to be an always-on, auto-restarting browser process (a
launchdagent launching Chrome with--remote-debugging-port+KeepAlive=trueunder a separate profile) that was winning the race to "be the browser" and capturing the extension's native-messaging pairing before my real interactive session could. Extension showed installed/logged-in the whole time — looked identical to what you're describing.Worth a quick check on your end even though it's Linux/Brave:
plus anything else on the box (an agent framework, MCP tool, scraper, CI runner) that might spin up a persistent Brave/Chromium instance with its own profile and an auto-restart policy. If something's holding a
--remote-debugging-portopen or auto-relaunching a browser process in the background, killing/disabling it and doing a full browser restart is worth trying before digging further into the service-worker/native-host handshake itself.If nothing like that turns up, this is probably unrelated to what fixed it for me, and the failure mode you found (session↔host discovery breaking on Linux even with a healthy extension↔host link) sounds like a distinct bug worth tracking separately from mine.
Closing as resolved on my end — root cause and fix posted above. Reopen if others confirm the same cause, or if quinnjr's Linux repro turns out to be distinct (looks like it might be).