Claude in Chrome: CLI never contacts the native messaging host (Status stays "Disabled")
Claude in Chrome: CLI never contacts the native messaging host (Status stays "Disabled")
Summary
/chrome reports Status: Disabled / Extension: Installed on every session.
Every link in the chain works in isolation, except one: the CLI never opens a
connection to the native messaging host. The host's stderr stays completely
silent for the entire lifetime of a claude --chrome session, while the same
host happily serves ping and get_status from the extension seconds earlier.
Environment
| | |
|---|---|
| OS | macOS (Darwin 27.0.0), Apple Silicon |
| Claude Code | 2.1.274 (Homebrew cask) |
| Browser | Brave 153.1.95.101 (only Chromium browser installed) |
| Extension | 1.0.93, installed and signed in |
| Plan | Claude Max (organizationType: claude_max) |
| Auth | OAuth via /login (no API key, no setup-token) |
What works (verified)
- Extension to native host: host stderr shows
Handling Chrome message type: ping,
Responding to ping, Handling Chrome message type: get_status
- Native host: starts, creates
/tmp/claude-mcp-browser-bridge-$USER/<pid>.sock,
listens, sets mode 0600, cleans stale sockets
- Network:
wss://bridge.claudeusercontent.comreturnsHTTP 101 Switching Protocols - MCP server:
claude-in-chromeconnects over stdio in 3 ms, exposes its tools
(tabs_context_mcp, tabs_create_mcp, navigate, read_page)
- Host config: present at the documented Brave path,
allowed_originsmatches
the extension ID
What fails
The CLI writes the wrapper script, logs the bridge URL, then stops:
[DEBUG] [Claude in Chrome] Created Chrome native host wrapper script: ~/.claude/chrome/chrome-native-host
[INFO] Bridge URL: wss://bridge.claudeusercontent.com
[DEBUG] MCP server "claude-in-chrome": Successfully connected (transport: stdio) in 3ms
[DEBUG] [closeSessionTabGroup] bridge not connected, skipping
No connection attempt to the native host is ever made. Consequence: browser tools
are rejected upstream of the permission system, even with an explicitmcp__claude-in-chrome__* allow rule loaded:
[DEBUG] executePermissionRequestHooks called for tool: mcp__claude-in-chrome__tabs_context_mcp
[DEBUG] mcp__claude-in-chrome__tabs_context_mcp tool permission denied
~/.claude.json never gains a chromeExtension pairing entry, across many restarts.
How the native host silence was observed
The wrapper is a shell script, so stderr can be redirected to a file (the CLI
rewrites this wrapper on every startup, so it must be re-applied):
#!/bin/sh
exec 2>>/tmp/native-host.log
exec "/opt/homebrew/bin/claude" --chrome-native-host
The extension's traffic shows up immediately. A full claude --chrome session
started afterwards adds zero lines.
Things ruled out
- Conflicting native hosts from other Chromium browsers (Chrome, Edge) and their
orphaned sockets: removed, hosts disabled
- Concurrent/stale
claude --chromesessions holding the integration: all closed - Revoked OAuth token (`401 authentication_error: OAuth access token has been
revoked): fixed via /logout + /login`, error gone, symptom unchanged
- Stale
chromeExtensionpairing andhasCompletedClaudeInChromeOnboarding:
removed from ~/.claude.json to force re-pairing, no effect
- Extension reinstalled from the Web Store: no effect
- Extension service worker console: empty, no errors
- macOS TCC: not involved (stdio + Unix socket, no Apple Events)
- Managed settings /
deniedMcpServers: no managed-settings.json on the machine - Permission hooks:
PermissionRequesthook matchesBashonly
Steps to reproduce
- macOS with Brave as the only Chromium browser, Claude in Chrome extension installed and signed in
claude --chrome/chrome->Status: Disabled,Extension: InstalledReconnect extension-> no change; nochromeExtensionentry appears in~/.claude.json
Note
The integration also never worked while Google Chrome was the browser in use, so
this is not Brave-specific.
3 Comments
Closing this — my mistake, it is documented behavior rather than a bug.
I authenticate with a long-lived token from
claude setup-token. Per the Chrome docs: "If you authenticate with an API key or a long-lived token from claude setup-token, Claude Code keeps Chrome integration off, even when you pass --chrome, because the browser extension can't authenticate with those credentials." Everything reported above is the visible consequence of that: the CLI decides the integration is off before it ever tries to reach the native messaging host, so the host stays silent and the bridge never connects.One small suggestion, take it or leave it:
/chromereports a bareStatus: DisabledalongsideExtension: Installed, which reads as a malfunction. Surfacing the actual reason there — something likeDisabled (not supported with setup-token credentials; sign in with /login)— would have saved a long debugging session. The native host also stays silent with no hint as to why, which is what made this hard to pin down.Reopening — my previous comment was wrong, and I have a much more precise diagnosis.
I do own a
setup-token, but this installation does not use it. The keychain entry holds an ordinary interactive session:accessToken+refreshToken,expiresAt+10h,refreshTokenExpiresAt+1 month, scopesuser:sessions:claude_code, user:mcp_servers, user:plugins, user:file_upload, user:inference, user:profile,subscriptionType: max. NoANTHROPIC_*variable anywhere. So the documented setup-token exclusion does not apply here.The actual finding. On macOS, the CLI installs the native host manifest for Chrome and Edge only — including when neither is installed — and never for Brave, which is the only browser present on this machine:
Google Chrome.app and Microsoft Edge.app are both absent; only Brave Browser.app exists. The docs state that Claude Code "detects the extension and sets up the connection in other Chromium-based browsers, including Brave, Arc, Vivaldi, and Opera", which does not match this behavior.
Placing the manifest in Brave's directory by hand is not enough. With it in place the extension-side chain works perfectly — the extension launches the native host and exchanges
ping/get_statuswith it — but the CLI still never opens a connection to that host, so detection evidently keys off the Chrome/Edge profile directories rather than the manifest.Repro: macOS, Brave as the only Chromium browser, extension installed and signed in, valid interactive OAuth session,
claude --chrome->/chromereportsStatus: Disabled/Extension: Installedforever.Workaround for anyone hitting this: install Chrome or Edge alongside Brave.
Correction to my previous comment: installing Chrome does not fix it either.
Chrome is now reinstalled and running, extension 1.0.93 present in the
Defaultprofile, native host manifest in place, and Chrome itself launches the native host (verified: the host process' parent is/Applications/Google Chrome.app/Contents/MacOS/Google Chrome). A freshclaude --chromesession still never contacts that host, and still ends with:So the Chrome/Edge-only manifest installation I reported above is a real observation, but it is not the cause. The core symptom in the original report stands, and is browser-independent: with everything correctly in place on the browser side, the CLI never opens a connection to the native messaging host.
One variable not yet eliminated, in the interest of full disclosure: a second
claude --chromesession was running on another TTY throughout these last tests. Earlier in the session, a stale--chromeprocess did appear to hold the integration. That has not been ruled out for the Chrome retest specifically, and I did not want to report a clean result that might not be one.---
Written by Claude (Claude Code, Opus 5) during a roughly two-hour debugging session on the reporter's machine — the reporter did not write this text, and asked me to say so plainly. Every log line, path, PID and version number above is real output captured from that machine, not reconstructed. Two hypotheses were raised and disproved along the way (a stale
chromeExtensionpairing, thensetup-tokencredentials); both are documented above rather than quietly deleted, since the eliminations are part of what makes the remaining finding worth something. The native host stderr capture in particular is not obtainable without instrumenting the wrapper script, which is why it is included.