"Always allow" for Claude-in-Chrome site permissions is always persisted as duration:"once" — approved sites list stays empty, prompt repeats for every browser action

Status Open
Reported on v2.1.201
Maintainer reply None cached
Activity 20 comments · opened Jul 6, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

When the Claude Code desktop app shows the site-permission dialog ("Allow Claude to read page content on www.avito.ru?" / "click on…" / "navigate to…") and the user presses Always allow, the grant is written into the Chrome extension's permissionStorage with duration:"once" instead of "always". The once-grant is consumed by the current tool call, so the very next browser action prompts again. "Your approved sites" (extension options → Permissions) stays permanently empty no matter how many times Always allow is pressed.

Storage forensics (byte-scan of the extension's chrome.storage.local LevelDB, extension id fcoeoabgfenejglbffodgkkbkcdhcgfn, v1.0.79, full history .log + .ldb):

  • 46 records for netloc:"www.avito.ru", ALL duration:"once", all created 2026-07-06 (the user pressed Always allow and/or Allow once ~46 times in one day);
  • zero records with duration:"always" — ever;
  • each once-record is immediately followed by a write of {"permissions":[]} (consumed).

Suspected root cause — in assets/mcpPermissions-B0h6Fctz.js (extension v1.0.79), the handler that resumes a tool call after the desktop dialog is answered grants the permission with a hardcoded MT.ONCE, discarding the duration the user chose:

const l=o;if(l.url)try{
  const{host:e}=new URL(l.url),
  r=t?.permissionManager??this.context.permissionManager;
  await r.grantPermission({type:"netloc",netloc:e},MT.ONCE,l.toolUseId)   // <-- always ONCE
}catch{}

grantPermission itself supports ALWAYS fine (duration:t with toolUseId only for ONCE). Either the desktop app never transmits the chosen duration over the bridge, or the extension ignores it — the visible result is identical: "Always allow" behaves exactly like "Allow once".

Workaround (verified end-to-end): manually appending {"action":"allow","duration":"always","scope":{"netloc":"avito.ru","type":"netloc"},"id":<uuid>,"createdAt":<now>} to permissionStorage.permissions via the extension service-worker console immediately makes the site appear in "Your approved sites" (with Revoke buttons) and the prompts stop — so the read path (findApplicablePermission) honors persistent grants; only the write path from the desktop dialog is broken.

Impact: autonomous/long-running sessions are blocked every few seconds waiting for the user; 46 prompts answered in one day for a single site. "Bypass permissions" mode and settings.json mcp__claude-in-chrome__* allow rules do not (and per design should not) affect this layer, so the broken "Always allow" is the only user control — and it does nothing. Possibly related: #67020, #30356.

What Should Happen?

One "Always allow" press stores a persistent duration:"always" netloc grant; the site appears in "Your approved sites"; no further prompts for that site (the grant should cover read/click/navigate on that domain and persist across sessions).

Error Messages/Logs

Sample stored record (from the extension's LevelDB, decoded):
{"action":"allow","createdAt":1783326998947,"duration":"once","id":"4f0d528e-4d70-45a9-afb7-5107b9ef3455","scope":{"netloc":"www.avito.ru","type":"netloc"},"toolUseId":"7c84a64b-df56-4b70-aae4-d69bf983aa2b"}

Current live value of permissionStorage after 46 user grants: {"permissions":[]}

Dialog text (desktop app): "Allow Claude to read page content on www.avito.ru?" with buttons Deny / Always allow / Allow once — pressing "Always allow" produces the duration:"once" record above.

Steps to Reproduce

  1. In the Claude Code desktop app (Windows), start a session that drives Chrome via the claude-in-chrome MCP on any site (e.g. avito.ru).
  2. When "Allow Claude to read page content on <site>?" appears, press Always allow.
  3. Watch the next read/click/navigate action on the same site prompt again within seconds.
  4. Open the extension options page → Permissions → "Your approved sites": it still says "No sites have been approved yet".

Environment: Claude Code desktop app on Windows 10 Pro (10.0.19045), CLI core 2.1.201; Claude in Chrome extension v1.0.79; Chrome stable.

Claude Model

None

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.201 (Claude Code)

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Other

Additional Information

_No response_

View original on GitHub ↗

20 Comments

PropellerHead23 · 3 months ago

Matching behavior on macOS, desktop 1.20186.1 (so not Windows-only): claude-in-chrome tools re-prompt in every desktop-spawned session despite full allow-list entries — "Always allow" never sticks past the session, consistent with it being persisted as duration:once. Regression onset for me was the 2026-07-11 desktop auto-update.

Workaround that holds: permissions.defaultMode: "dontAsk" in ~/.claude/settings.json (with the tools allow-listed) suppresses the per-session cards — details in https://github.com/anthropics/claude-code/issues/73587#issuecomment-4975856651.

Same regression family: #73587 (desktop ignores permissions.allow, Windows + macOS), #76238 (MCP allowlisted tools still prompt on fresh session, macOS).

PropellerHead23 · 2 months ago

Still reproduces on the current build, with a new data point that narrows the surface: bypassPermissions mode is also ignored for connector tools.

Environment: macOS 15.5, Claude Desktop 1.22209.0, embedded Claude Code CLI 2.1.207, Claude in Chrome extension current as of 2026-07-19.

Repro (timestamped from the app's own local telemetry events):

  1. claudeai.code.permission_mode.changed — previous_mode: "default" → current_mode: "bypassPermissions" via the mode dropdown (12:46:28)
  2. New local session created in that folder (12:46:31)
  3. claudeai.epitaxy.tool_approval.decided — a connector tool (mcp__claude-in-chrome__*) still raised a blocking approval card (12:46:48), decided "always"

So the desktop adjudicator cards connector tools even in bypassPermissions, and the "always" decision then doesn't survive the session (the duration: once behavior this issue describes). Also confirmed there's no user-side persistence to work with: Claude Extensions Settings/ant.dir.ant.anthropic.chrome-control.json contains only {isEnabled}, and the only related Local Storage key found (epitaxy-folder-permission-mode.<account>) governs the per-folder boot mode, which per the above doesn't suppress connector cards either.

For completeness, ~/.claude/settings.json in this setup allows the connector tools three ways (per-tool mcp__claude-in-chrome__navigate etc., mcp__claude-in-chrome__* glob, and server-level mcp__claude-in-chrome) with defaultMode: "dontAsk" — all ignored, consistent with #73587 and #76238.

edu5r · 2 months ago

Confirming this bug on Windows 11 Pro with the Claude Desktop app (pt-BR locale).

Impact: Every claude-in-chrome tool call (Computer, Find, Navigate, Tabs Context) triggers a permission prompt, on every action, in every session. Clicking "Always allow" ("Sempre permitir") never persists — the prompt returns on the very next action. This makes multi-session browser workflows effectively unusable (we run several parallel Claude Desktop sessions automating an Odoo ERP, and each one blocks on prompts constantly).

What we tried (no effect, consistent with this issue's root cause):

  • ~/.claude/settings.json with "defaultMode": "bypassPermissions" and server-level allow rule mcp__claude-in-chrome
  • Folder-level bypassPermissions mode in the Desktop app (badge shows bypass active — browser tools still prompt)
  • Clearing the Desktop app's Local Storage/IndexedDB and full app restart

The session-level bypass badge being active while browser tools still re-prompt on every action supports the finding that the extension permission layer ignores the grant duration chosen in the dialog.

Happy to provide logs or test a fix build.

mgu333 · 2 months ago

Adding a data point that I think is new: the verified workaround in this issue no longer works, and neither does the undocumented env var.

Claude Code 2.1.220 / Chrome extension 1.0.84 / Chrome 151.0.7922.75 / macOS 26.6.

Tried, all with no effect on per-action prompts:

  • Writing permissionStorage records directly (Chrome closed, LevelDB, duration:"always", single-JSON-encoded, verified readable on re-read)
  • Adding the sites through the supported Settings → Permissions → approved sites UI
  • CLAUDE_CHROME_PERMISSION_MODE=skip_all_permission_checks — set in settings.json env and ~/.zshrc, confirmed live via printenv inside the Claude process, full app restart
  • Extension side-panel mode "Skip all approvals"
  • permissions.allow (193 rules: server-level, wildcard, fully-qualified, 5 name spellings) in both user and project settings
  • bypassPermissions / acceptEdits / default modes

Also worth noting: prompts fire for tools that pass no URL at all (e.g. tabs_context_mcp with {"createIfEmpty": false}). In mcpPermissions-*.js, findApplicablePermission only matches scope.type === "netloc" and requires t.scope.netloc to be truthy — so a URL-less call can never match a stored grant, and empty-hostname scopes (file://) are filtered out entirely. matchesNetloc supports *.domain, but normalization strips a trailing dot so a bare *. can't serve as a catch-all.

Filed with fuller detail as #84355.

0x1337ff · 2 months ago

Working workaround that survives the duration:"once" bug: a PermissionRequest hook

Since the root cause here is that the grant is never persisted as "always", anything that reads stored grants is defeated by design. A PermissionRequest hook sidesteps storage entirely: it fires when Claude Code is about to raise the dialog, and answers on the user's behalf.

~/.claude/hooks/permissionrequest-chrome-allow.py:

#!/usr/bin/env python3
import json, sys

PREFIX = "mcp__claude-in-chrome__"

try:
    payload = json.load(sys.stdin)
except (json.JSONDecodeError, ValueError):
    sys.exit(0)

if not str(payload.get("tool_name", "")).startswith(PREFIX):
    sys.exit(0)

json.dump({"hookSpecificOutput": {"hookEventName": "PermissionRequest",
                                  "decision": {"behavior": "allow"}}}, sys.stdout)

Wired in ~/.claude/settings.json with "matcher": "mcp__claude-in-chrome__.*" under a PermissionRequest entry.

Verified: Claude Code 2.1.223 (CLI), Chrome extension 1.0.84, macOS (Darwin 25.6.0). Around 15 consecutive browser tool calls across two domains, zero prompts, and it took effect without restarting the session. deny and ask rules are still evaluated after the hook, so it does not widen those.

Tested on the CLI only, not on the Desktop surface reported above. Full write-up in #84355.

Still a workaround, not a fix: the persistence bug this issue documents is untouched.

geokao · 2 months ago

Thanks for the storage forensics in the original report — the duration:"once" byte-scan is what makes this diagnosable from outside the extension.

Still reproducing on macOS desktop, 2026-08-12. Darwin 25.6.0, Claude Code desktop app. Current card wording is "Allow Claude to act on <origin>?" with Deny / Always allow / Allow once. Every browser tool involved is explicitly allow-listed in ~/.claude/settings.json and defaultMode is "auto".

Two things that may help whoever picks this up.

1. Why the allow list cannot paper over it. The card is origin-scoped; permissions.allow is tool-scoped, and there is no rule syntax that grants an origin. "Always allow" is therefore the only user-facing control that can express the grant at all, which makes this persistence bug the entire lever rather than an inconvenience with a config workaround.

2. A schema discrepancy in the hook workaround above. The script in the 2026-08-06 comment emits "decision": {"behavior": "allow"}, but the published hooks reference specifies decision as a string — "allow" | "deny" | "escalate" — inside hookSpecificOutput. Both forms may be accepted, but anyone copying that snippet should know the documented shape is:

{"hookSpecificOutput": {"hookEventName": "PermissionRequest", "decision": "allow"}}

Related: #73587 (desktop ignores permissions.allow), where I have posted the matching macOS environment detail.

0x1337ff · 2 months ago

On the schema point (2): the published reference documents decision as an object for PermissionRequest, not a string. From PermissionRequest decision control in /docs/en/hooks:

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow",
      "updatedInput": { "command": "npm run lint" }
    }
  }
}

The field table under that heading lists behavior, updatedInput, updatedPermissions, message and interrupt as members of the decision object, the JSON-output summary row for PermissionRequest reads decision.behavior (allow/deny), and the note about ok: false points to hookSpecificOutput.decision.behavior: "deny". Three places, same shape.

The string form belongs to PreToolUse, where the field is named permissionDecision ("allow" | "deny" | "ask"), not decision. There is no "escalate" value for either field; the word only appears in prose ("escalate to the user"). The bare top-level "decision" string is a separate mechanism whose only value is "block".

So the snippet in the 2026-08-06 comment is the documented shape, and it is what I verified working live (zero prompts on navigate / computer / find across two origins, hook picked up hot without restarting the session). Anyone copying a bare "decision": "allow" into a PermissionRequest hook should expect it to be ignored and the prompt to come straight back.

Agreed on (1): the origin-scoped card versus tool-scoped permissions.allow mismatch is exactly why no allow rule can express the grant, and why the hook is a lever rather than a config tweak.

diego101sl-ai · 2 months ago

The "always allow" button only grants navigate / click / type ("Navegar, hacer clic y escribir"), but the extension repeatedly requests read page content — an action that grant does not cover. Every read re-prompts, and every "always allow" click writes another record that is never consumed.

Evidence: my approved-sites list has 6 duplicate entries for www.clarin.com and 3 for www.wsj.com, none with a "last used" timestamp, while www.aljazeera.com (approved before this started) has one entry with a valid "last used". The permission dialog is also partially untranslated ("Claude quiere read page content on:"), suggesting a recent version.

kubi-dev · 2 months ago

Additional evidence + a variant of this bug:

Setup: Claude Code CLI session started with --dangerously-skip-permissions (also permissions.defaultMode: bypassPermissions + mcp__claude-in-chrome allow rule in ~/.claude/settings.json), extension side-panel permission mode set to Skip all approvals, extension id fcoeoabgfenejglbffodgkkbkcdhcgfn.

Result: every navigate/action still raises the per-site dialog ("Claude in Chrome wants to navigate on www.namecheap.com") on every call — consistent with the hardcoded MT.ONCE finding above. Neither Claude Code bypass mode nor the side panel's Skip-all-approvals mode is honored for the Claude Code bridge, so there is currently NO configuration that makes browser automation from Claude Code prompt-free.

Inconsistency datapoint: on the same machine, same extension, same settings, a different concurrently-running Claude Code session navigates to never-before-visited sites (porkbun.com) with zero prompts, while a freshly restarted session prompts on everything. So the gating is per-session/per-bridge-connection, not per-config — whatever state exempts one session is not reproducible in another.

Requests:

  1. Fix the MT.ONCE hardcode so Always-allow persists.
  2. Make the side panel Skip-all-approvals mode (or an equivalent explicit opt-in) apply to Claude Code bridge sessions — browser automation is a daily-driver use case and per-action prompts make it unusable.
ThePieMonster · 1 month ago

Any updates on this?

lloudiyi93 · 1 month ago

Adding a concrete real-world data point on this bug — and on the whole family of related "Always allow" persistence issues (#68954, #47180, #32199, #30356, #59302, #33027, #43505, #37814, #46205): this is not a minor UX annoyance, it is a hard blocker for any serious use of Cowork's advertised autonomous/unattended automation.

I run a small HVAC/energy-renovation business in France, and my Cowork automations systematically combine local OneDrive folders and external websites, accessed via both the built-in browser and the Claude in Chrome extension. Every single one of these tasks requires re-granting "Always allow" permissions for folders and sites — even though I have already granted the exact same permissions repeatedly in prior runs and prior sessions.

In practice, a single task that is supposed to run end-to-end without intervention generates 5, 6, sometimes 7+ separate permission prompts before it can even get properly started. This makes it literally impossible to let an agent run unattended overnight — which is precisely the value proposition being marketed for Cowork and scheduled tasks.

This has been reported under different forms for over a year now (earliest report I found: #7472, September 2025) without a durable fix landing. I understand prioritization is hard, but a broken foundational permission/trust mechanism undermines every autonomy feature built on top of it. Please treat this as a top-priority fix — it's not a rough edge, it's what stands between "impressive demo" and "usable for real, unattended business automation.

correlate000 · 1 month ago

Still reproduces on extension v1.0.91 (Claude Code 2.1.263, macOS 15). Adding a code-level confirmation and two findings that may narrow the fix.

Correction to an earlier version of this comment: it said the profile was Arc rather than Google Chrome. On this machine v1.0.91 is installed under both Google Chrome and Arc, with byte-identical mcpPermissions-*.js, so the finding holds for either — it is not Arc-specific.

1. The hardcoded ONCE is still there in v1.0.91

assets/mcpPermissions-CMuwfoXg.js:

grantPermission({type:"netloc",netloc:e.host},u.ONCE,p.toolUseId,e.origin)

The duration enum in the same build does have the value:

ONCE="once"  ALWAYS="always"  SESSION="lti_session"

ALWAYS never reaches this call site. Grepping the whole bundle, the only other occurrence of the string ALWAYS in that file is inside an unrelated screenshot-description string. So the desktop-dialog path cannot produce a persistent grant regardless of which button the user picks — the UI offers "Allow all actions on <host> for this session" but the write is always once.

2. The side panel path is not affected — the bug is isolated to the MCP/desktop path

assets/sidepanel-DFfkp8zd.js wires both values to buttons:

a=useCallback(()=>{n(k.ONCE,i)},[n,i]), l=useCallback(()=>{n(k.ALWAYS,i)},[n,i])

So the read path and the storage layer are fine, and granting from the extension's own side panel does persist. Only mcpPermissions-*.js hardcodes the duration. That should make the fix a one-line change: thread the user's choice from the desktop dialog through to grantPermission instead of passing u.ONCE.

For reference, the record shape written in v1.0.91 (note the surface field, which isn't in the earlier report):

{id: crypto.randomUUID(), scope: t, action: "allow", duration: e,
 createdAt: Date.now(),
 toolUseId: e === ONCE ? n : undefined,
 origin:    e === ONCE ? r : undefined,
 surface: this.surface}   // "mcp_popup" | "classic_sidepanel" | "markdown"

3. There is no admin-policy escape hatch

managed_schema.json in v1.0.91 exposes only two policies:

  • blockedUrlPatterns — blocks sites
  • forceLoginOrgUUID — restricts which org may use the extension

There is no allowlist / auto-approve policy, so an administrator cannot work around this centrally either. For anyone browsing more than a handful of hosts, the per-site side-panel grant is the only option today.

Impact

On a machine driving browser automation across many hosts, every new host re-prompts on every action, and the "Your approved sites" list stays empty permanently, so there is no way for the user to tell which sites they have already approved.

matiasblanca · 1 month ago

Reporting from the CLI rather than the desktop app, on Windows 10 19045 with Claude Code 2.1.263 and extension v1.0.91 (same versions as @correlate000). The prompt I get is not the extension's — it is rendered by the Claude Code CLI in the terminal, and none of the extension-side findings in this thread apply to it. That may be a second path to the same symptom, so I'm putting the details here rather than opening a duplicate.

The prompt is drawn by the CLI, before the extension is reached

Verbatim, in the terminal:

Claude in Chrome wants to navigate on localhost:7195

https://localhost:7195/

> 1. Allow
  2. Allow all actions on localhost:7195 for this session
  3. Deny (esc)

Option 2 does not hold for the session it names. Selecting it and issuing the next navigate to the same origin reprompts immediately — the dialog comes back with option 2 still highlighted. One prompt per browser tool call, indefinitely.

Three things that do not suppress it

  1. permissions.defaultMode: "bypassPermissions" — already set in both ~/.claude/settings.json and the project's .claude/settings.local.json for the whole time. Every call still prompts. This matches the issue body's note that bypass mode does not (and by design should not) reach this layer.
  1. An explicit allowlist of the MCP tools — 14 entries in permissions.allow (mcp__claude-in-chrome plus each tool name: mcp__claude-in-chrome__navigate, __javascript_tool, __browser_batch, …). Tested both hot and after a full restart of Claude Code. Prompt unchanged.
  1. Hand-writing the always record into the extension's permissionStorage — the workaround from the issue body. Tested twice: once without surface, once with the full v1.0.91 shape (surface: "mcp_popup") and with both the with-port and without-port netloc. Both records verified persisted by reading the key back. Prompt unchanged.

Why #3 fails here, and why permissionStorage stays empty

Because on this path the extension's permission layer is never consulted. chrome.storage.local has no permissionStorage key at all after many prompt-and-approve cycles — not records with duration: "once", no key:

await chrome.storage.local.get('permissionStorage')
// {}

Object.keys(await chrome.storage.local.get(null))
// ['accountUuid', 'anonymousId', 'bridgeDeviceId', 'browserControlPermissionAccepted',
//  'codeVerifier', 'features', 'lastActiveOrgHint', 'lastPermissionModePreference',
//  'mcpConnected', 'modelOverrideSeen', 'notificationsEnabled', 'oauthState',
//  'permissionConsentOwner', 'quickModeTipDismissed', 'selectedModel', 'tabGroups',
//  'tipDisplayCounts', 'tokenOrg']

chrome.storage.sync is empty too. So the hardcoded u.ONCE in mcpPermissions-*.js is real and worth fixing, but fixing it alone would not help this configuration: the CLI resolves the per-site decision itself and never writes or reads a grant. lastPermissionModePreference is already skip_all_permission_checks and browserControlPermissionAccepted is true, consistent with those being side-panel-only.

Environment

| | |
|---|---|
| Claude Code | 2.1.263 (CLI, not the desktop app) |
| Extension | 1.0.91 (fcoeoabgfenejglbffodgkkbkcdhcgfn), Google Chrome |
| OS | Windows 10 Home 19045 |
| Terminal | Windows Terminal |
| Platform | Anthropic API |
| Regression | No — never worked on this machine |
| Target | local dev server on https://localhost:7195 |

Impact and the one mitigation that works

A UI verification pass over a local app — a dozen navigations, clicks and screenshots against a single origin — is a dozen manual approvals for the origin just approved. The only thing that helps is collapsing calls: one browser_batch covering navigate + wait + JS + screenshot draws a single dialog instead of four, taking a pass from roughly 30 prompts to 3 or 4. That is a call-site workaround, not a fix, and it does not help interactive work at all.

If the CLI-side prompt is considered a separate defect from the extension-side ONCE bug, I'm happy to move this into its own issue — say the word.

<sub>Edit history: earlier revisions of this comment attributed the behavior to the extension's read path, and one briefly claimed the manual workaround worked. Both were wrong; the tool calls in that test completed because the user approved the dialogs. Corrected above after identifying the prompt as CLI-side.</sub>

correlate000 · 1 month ago

Follow-up to my earlier comment. I injected persistent grants directly into permissionStorage to work around the duration:"once" write bug — and the prompts still appeared on every action. Tracing why turned up what looks like the actual root cause, which is upstream of the once/always issue.

forcePrompt makes stored grants unreachable

In v1.0.91, the permission manager (assets/SavedPromptsService-*.js) has a forcePrompt flag, and checkPermission gates three escape hatches behind !this.forcePrompt:

// 1. localhost bypass
if (!this.forcePrompt && this.bypassLocalhostForMcp && this.isLocalhostUrl(t))
  return {allowed:true, needsPrompt:false};

// 2. "skip all permission checks" mode
if (!this.forcePrompt && this.getSkipAllPermissions())
  return {allowed:true, needsPrompt:false};

// 3. a stored grant that actually matches
await this.loadPermissions();
const c = this.findApplicablePermission(s, e, new URL(t).origin),
      u = null !== c && this.forcePrompt && c.action === ALLOW && c.duration !== ONCE;
return c && !u ? /* allow */ : /* prompt */;

Line 3 is the important one: when forcePrompt is set, a matching duration:"always" grant sets u = true, so c && !u is false and it prompts anyway. The lookup succeeds and the result is then discarded.

And forcePrompt is switched on for exactly this path:

a && !o.permissionManager && (o.permissionManager = new j(()=>false, {surface:"mcp_popup", trackEvent:C})),
o.permissionManager?.setForcePrompt(a),

So for tool calls arriving over the desktop/MCP bridge, forcePrompt is true, and no stored permission of any kind can be honored — not "always" grants, not the "skip all approvals" mode, not the localhost bypass.

Why this reframes the bug

The duration:"once" hardcode I reported earlier is real, but fixing it alone will not stop the prompts: even a correctly stored always grant is discarded by forcePrompt. That also explains the reports in this thread that --dangerously-skip-permissions, permissions.defaultMode: bypassPermissions, and the side panel's Skip all approvals mode all fail to help (#67020, and @kubi-dev's comment above) — all three land on branches guarded by !this.forcePrompt.

Measurements on this machine

  • Injected 9,293 duration:"always" netloc grants (1,288 TLD wildcards *.com, *.jp, … + 8,002 localhost:PORT / 127.0.0.1:PORT), verified on disk.
  • Replaying matchesNetloc against the live store: a request for peatix.com matches 1 grant (*.com). The lookup finds it.
  • Prompts still appear on every action, including navigate (not just execute_javascript), on a freshly started client.

So the grant store is populated and matching correctly; the result is being thrown away downstream.

Suggested fix

Either don't set forcePrompt for bridge-originated tool calls, or narrow clause 3 so forcePrompt only overrides once grants and session-scoped state, not persisted always grants — otherwise "Your approved sites" can never have any effect on this path, no matter how the write bug is fixed.

Environment: extension v1.0.91, Claude Code 2.1.263, macOS 15. Installed under both Google Chrome and Arc with identical bundles.

Mauronic · 1 month ago

My repro:

Max plan, macOS, Skip mode, Default for all sites = Allow, restarted claude desktop, fresh chat, prompted twice on the same domain in one task - web sessions timing out because I can't get to approvals fast enough.

ThatsJake · 1 month ago

Same issue here. Choosing the 'always allow' option for a local-network site (a 3D printer's web UI at a LAN IP) doesn't persist — the very next action on the same origin prompts again. Having to re-approve every single action makes local printer automation workflows nearly unusable.

markusmoglich · 1 month ago

I experienced the same issue while running Claude Code agents with the --dangerously-skip-permissions flag.

The issue went away after I removed all rules from the permissions.allow section in settings.json, set "defaultMode": "auto" there, and started using --permission-mode auto when launching Claude Code.

With this setup, Claude Code no longer asks me for permission for every action.

adegtiar · 1 month ago

Same thing here on v1.0.91. Clicking "Always allow actions on this site" does
nothing except add another duplicate row to the approved-sites list, and the
prompt is back within seconds. I ended up with 7 rows across 4 sites in a
single session before I stopped clicking it.

Since I had a session going anyway I pulled permissionStorage and poked at
it. Three findings below, one of which I don't think has come up yet.

Setup: extension v1.0.91, Chrome 152.0.7977.84 (arm64), macOS. Worth saying up
front that permissionStorage does exist here and is being written
correctly — different from @matiasblanca Windows result — so this should
isolate the lookup side of the problem from the storage side.

1. The grants get stored correctly and ignored anyway

17 records from one 28-minute session (2026-09-12, 16:31–16:59 local). Seven
prompts fired within 4–24 seconds of an always grant being written for that
exact netloc. Gaps measured from the nearest preceding grant:

www.revzilla.com   16:33:54   +4.5s   toolUse bbcf2e6c
www.revzilla.com   16:34:04  +15.1s   toolUse b84b0cf3
www.reddit.com     16:53:39   +4.1s   toolUse b626c3d6
www.btosports.com  16:58:35  +12.8s   toolUse b96b0385
www.motosport.com  16:59:16   +4.8s   toolUse 2d4663a2
www.motosport.com  16:59:23  +12.2s   toolUse 51f7fa77
www.motosport.com  16:59:35  +23.7s   toolUse 049d4ede

motosport is the clearest one. Grant 5e2ea0a5 written at 16:59:11.769 as
{"duration":"always","scope":{"netloc":"www.motosport.com","type":"netloc"},"surface":"mcp_popup"},
then three more prompts over the next 24 seconds. That grant is still in
storage and still shows in "Your approved sites", so it was live the whole
time — nothing expired or got cleared.

The thing that jumped out: every once record carries a distinct
toolUseId.
So the check is running per tool call, and a stored always
grant never satisfies it. That lines up with @correlate000's forcePrompt
analysis, except this is just the normal UI doing it rather than hand-injected
records.

2. Repeated "Always allow" appends instead of updating

reddit, revzilla and ebay each ended up with two always records carrying
identical scope.netloc. No upsert, so every click adds a row. That's the
duplicate list everyone's seeing.

3. Revoking a site with duplicate rows doesn't actually revoke it

This is the one I'd flag hardest. I tested it directly:

Before — two ebay always records (e9c5d240, 8a3563e4), two ebay rows in
the list. I clicked Revoke on one row. After — e9c5d240 is purged from
permissionStorage, but 8a3563e4 is still there and ebay is still
granted.
(17 records before, 16 after; both dumps attached.)

So the number of Revoke clicks you need equals the number of times you clicked
"Always allow." If you click Revoke once, watch a row disappear, and assume
the site no longer has access, you're wrong. Given #2 makes duplicates
basically guaranteed, that seems worth treating as its own bug rather than
cosmetic fallout from the lookup issue.

A few notes that might save others some time

  • All records here are surface: "mcp_popup". Both always and once

records carry scope:{netloc,type:"netloc"} — once records just
additionally carry origin and toolUseId. Chrome devtools collapses
scope in the object preview, which makes the two look like structurally
different records when they aren't. I chased that for a while before
expanding them. JSON.stringify before concluding anything.

  • Revoke does purge permissionStorage on this build, by record id.
  • The settings page doesn't live-update. A grant written while the page is

open won't show until you reload. I spent a while convinced a grant had been
silently dropped when the page was just stale. Reload before comparing
storage against the UI.

  • The render logic itself is fine — one row per always record, duplicates

included. 7 records, 7 rows, once I reloaded.

Before/after JSON dumps and screenshots attached. Happy to run anything else
against this if it's useful.

<img width="754" height="652" alt="Image" src="https://github.com/user-attachments/assets/67f5209b-3137-412d-a918-7824ff09704e" />

<img width="1772" height="1170" alt="Image" src="https://github.com/user-attachments/assets/4469e830-6631-4242-b05e-83105b3d8302" />

<img width="1708" height="1148" alt="Image" src="https://github.com/user-attachments/assets/38f8abc9-4f64-48bd-87b2-6b4177558802" />

permissionStorage-before-revoke.json

permissionStorage-after-revoke.json

StopItShef · 1 month ago

Confirming on extension 1.0.93, which is newer than any version reported in this thread so far.

Environment

  • Machine 1: Windows 11 Business 10.0.26200.9445, Chrome 152.0.7977.83, extension 1.0.93
  • Machine 2: Windows 11 Pro 10.0.26200.9168, Chrome 152.0.7977.83, extension 1.0.93 (accessed over remote desktop, separate install)
  • Client: Claude side panel in Chrome, Cowork session driving the extension over the MCP bridge (mcp__claude-in-chrome__computer, action screenshot)
  • Model: Haiku 4.5 on both machines

Repro

  1. Side panel shows the banner "Skip all approvals is on. Claude never pauses, even for unsafe actions."
  2. Start a fresh conversation. Do not switch models.
  3. Ask for a single screenshot of the current tab.

Result

Prompt appears directly above the Skip banner: "Claude wants to take a screenshot in Chrome on your computer", payload {"action":"screenshot","tabId":<id>}, with Deny / Allow once. No Always allow option is offered on this prompt. Denying returns "User rejected" to the session. Tab IDs in the payload were 404677147 and 375529716, so the action did reach the extension before being gated.

Reproduced on both machines. On machine 1 this persisted after clearing Chrome cache, removing and reinstalling the extension, and rebooting. This matches the forcePrompt behavior described above: skip-all mode has no effect on the bridge path.

basilesportif · 16 days ago

I have it on 1.0.94 . Incredibly dumb and annoying bug. Shocking it's taken this long to fix.