Claude in Chrome: extension binds native host successfully but CLI always reports "Browser extension is not connected"

Status Fixed / completed
Reported on v2.1.226
Maintainer reply None cached
Activity 5 comments · opened Aug 8, 2026 · closed Aug 22, 2026

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, Chrome Default profile
  • 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, no disable_reasons.
  • Chrome running Default profile (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.json present,

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 binds
Desktop (/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.

View original on GitHub ↗

5 Comments

quinnjr · 15 days ago

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.json exists and points at the profile's chrome-native-host wrapper; allowed_origins matches the store extension ID.
  • Clicking the extension icon spawns the native host, and it stays running: .../claude/versions/2.1.233 --chrome-native-host.
  • CLI session and native host are the same version (2.1.233) — I specifically ruled out auto-update version skew: earlier the CLI had updated 2.1.231→233 across a long-lived session while Brave kept an old host alive; after killing stale hosts, restarting the CLI session (claude --continue --chrome), and restarting Brave so wrapper/host/CLI all aligned on 2.1.233, the result is unchanged.
  • /chrome completes without output; tabs_context_mcp still returns "Browser extension is not connected" every time.
  • Logged into the extension and CLI with the same claude.ai account, not API-key auth.

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.

OfficialUlix · 8 days ago

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: /chrome shows Status: 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 to JSON.parse() the literal "Browser extension is not connected..." error string). tabs_context_mcp and 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-host wrapper, and com.anthropic.claude_code_browser_extension.json manifest (allowed_origins correctly lists the installed extension ID) both check out and work fine when the wrapper is executed manually (echo '{"type":"ping"}' | ~/.claude/chrome/chrome-native-host immediately logs a clean startup + socket listener).
  • Found a version-skew bug in the wrapper itself: it was hardcoded to an old pinned binary path (~/.direnvdata/claude/versions/2.1.235) from a previous auto-update, while the active CLI had since moved to 2.1.239 — same class of bug as the Homebrew-Caskroom-path issue described in #84814. Fixed by pointing the wrapper at the stable claude symlink instead of a version-pinned path. Did not resolve the connection.
  • The real finding: ~/Library/Logs/Claude/chrome-native-host.log shows 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 in chrome://extensions, clicking "Reconnect extension" from /chrome (which opens a tab that lands on chrome://extensions and 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).
  • Checked for anything that could suppress native messaging: no MDM/config profiles (profiles list empty), no NativeMessagingBlocklist/related keys in ~/Library/Preferences/com.google.Chrome.plist or /Library/Managed Preferences/com.google.Chrome.plist.
  • The service worker console (chrome://extensions → Inspect views: service worker) shows no log output at all — not even after clicking "Reconnect extension" in /chrome while 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.

OfficialUlix · 8 days ago

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 launchd LaunchAgent keeping an always-on, automated Chrome instance running in the background for an unrelated browser-automation workflow:

~/Library/LaunchAgents/com.ulix.chatgpt-web-browser.plist

It launched the same Google Chrome application (not a separate binary) with:

--user-data-dir=~/.codex/chatgpt-web-profile
--remote-debugging-address=127.0.0.1
--remote-debugging-port=9222

plus both RunAtLoad=true and KeepAlive=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:

  • Opening Chrome "normally" would sometimes actually route into this automation instance.
  • The Claude extension's native-messaging pairing was binding to the socket in the automation profile instead of my normal profile.
  • The extension still showed as installed/logged-in, but the CLI got no usable tab group and reported "not connected."
  • Killing Chrome didn't fix it, because KeepAlive immediately 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_mcp started 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/systemd service, cron job, Docker container, or agent framework that launches Chrome/Brave/Chromium with --remote-debugging-port and 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.

OfficialUlix · 8 days ago

@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 launchd agent launching Chrome with --remote-debugging-port + KeepAlive=true under 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:

ps aux | grep -i remote-debugging-port
systemctl --user list-units --type=service --state=running
crontab -l

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-port open 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.

OfficialUlix · 8 days ago

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).