Chrome extension detection treats EPERM as "not installed", causing an unbreakable install-prompt loop without Full Disk Access

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 0 comments · opened Jul 26, 2026

Version: 2.1.220 | macOS Darwin 27.0.0 (arm64) | Chrome 150.0.7871.182
Host: Claude Code running in an IDE's integrated terminal (an app without macOS Full Disk Access)

Symptom

/chrome and the claude-in-chrome skill repeatedly prompt to install the Chrome extension when it is already installed. The connection flow never completes. The native messaging host manifest is never written.

Root cause

Extension detection reads ~/Library/Application Support/Google/Chrome, then looks for <profile>/Extensions/fcoeoabgfenejglbffodgkkbkcdhcgfn.

When the host process lacks macOS Full Disk Access, that readdir fails with EPERM (errno 1, "Operation not permitted") — not ENOENT. Verified directly on the affected machine:

FAIL errno=1 Operation not permitted  .../Google/Chrome
FAIL errno=1 Operation not permitted  .../Google/Chrome/Default/Extensions
OK   (0 entries)                      .../Google/Chrome/NativeMessagingHosts

Note that NativeMessagingHosts is readable/writable while the sibling profile data is not — macOS TCC protects the profile directory specifically, so the failure is isolated to the detection path.

The detection helper returns a boolean, so EPERM collapses into "not installed". The result is indistinguishable from a genuine absence, so the UI shows the install prompt with no indication that the check failed for permission reasons. There is no user-facing path out of the loop — reinstalling the extension, restarting Chrome, restarting Claude Code, and /chrome → reconnect all fail identically.

Secondary: the cache refresh only writes on a positive result (if (!t) return), so cachedChromeExtensionInstalled cannot distinguish "checked, absent" from "never successfully checked". It remained false in ~/.claude.json even after the integration started working.

Reproduction

  1. Install the Claude extension in Chrome.
  2. Run claude from an app without Full Disk Access (any IDE integrated terminal is the common case).
  3. Invoke /chrome. It prompts to install an extension that is already present, indefinitely.

Confirmation of diagnosis

Granting Full Disk Access to the host app and restarting it fixed it immediately:

  • the profile directory became readable (49 entries)
  • the extension was confirmed present at Default/Extensions/fcoeoabgfenejglbffodgkkbkcdhcgfn — it had been there the whole time
  • the native host manifest com.anthropic.claude_code_browser_extension.json was created within seconds, having never been written across three prior attempts
  • the mcp__claude-in-chrome__* tools registered successfully

Suggested fix

Return a tri-state (installed | not-installed | unknown) and map EPERM/EACCES to unknown, showing a permissions message naming the host app instead of the install prompt. That alone converts an unbreakable loop into a self-service fix.

Better: don't gate on the filesystem check at all. The authoritative signal is whether the extension answers over native messaging. Write the manifest and attempt the connection regardless, treating the directory scan as a fast path rather than a precondition. That removes the dependency on reading a TCC-protected path, which will otherwise break for any sandboxed or permission-restricted host.

Related

Likely the underlying cause of several reports closed as stale/duplicate without root-cause analysis: #72606, #37684, #40758.

View original on GitHub ↗