[Bug] Allow rules under `~/.claude/` show as loaded per /permissions but don't match at runtime

Status Fixed / completed
Reported on v2.1.133
Maintainer reply None cached
Activity 12 comments · opened May 8, 2026 · closed Aug 17, 2026

Summary

Allow rules in ~/.claude/settings.json that target paths under ~/.claude/
itself appear in /permissions as loaded but never match at runtime. Edits
to files in any subdirectory under ~/.claude/ prompt for permission
despite a matching rule being present and active.

Tested all four combinations of {tilde, absolute path} × {**/*.md glob,
literal path} for both Edit(...) and Update(...) scope-name forms —
none matched.

Reproducer

  1. Create a markdown file at ~/.claude/rules/test-rule.md (the rules/

subdirectory is the documented standard location for user rule files).

  1. Add to ~/.claude/settings.json:

``json
{
"permissions": {
"allow": [
"Edit(~/.claude/**/*.md)"
]
}
}
``

  1. Restart Claude Code, or run /hooks to force-reload settings.
  1. Verify with /permissions that the rule appears in the loaded allow list.
  1. Ask Claude Code to edit ~/.claude/rules/test-rule.md.

Expected (per docs): the Edit operation proceeds without a permission
prompt. The rule's gitignore-style ** matches the subdirectory, and the
docs explicitly state *"Edit rules apply to all built-in tools that edit
files."*

Actual: a permission prompt fires titled
Update(~/.claude/rules/test-rule.md). The rule is bypassed.

What was tested

Each rule below was added to ~/.claude/settings.json, settings reloaded
via /hooks, and /permissions confirmed each rule was loaded. None
matched the runtime edit attempt.

| Rule | Match? |
| ---------------------------------------------------------- | ------ |
| Edit(~/.claude/**/*.md) | No |
| Edit(//<absolute home>/.claude/**/*.md) | No |
| Update(~/.claude/**/*.md) | No |
| Update(//<absolute home>/.claude/**/*.md) | No |
| Update(~/.claude/rules/test-rule.md) (literal path) | No |
| Update(//<absolute home>/.claude/rules/test-rule.md) | No |

Notes

  • Auto-memory edits (under ~/.claude/projects/<encoded-slug>/memory/*.md)

are silent — they appear to have implicit allowlisting independent of the
~/.claude/** allow rules.

is documented as supported ("Path from home directory") and ** is
documented as recursive across directories.

  • The runtime prompt label uses Update(~/...) form, but /permissions

lists rules with whatever scope name (Edit or Update) was put in
settings.json. Update(...) rules with the same path patterns also
fail to match.

  • Whether project-level .claude/settings.local.json (e.g. with

defaultMode: "acceptEdits" and project-relative Edit(.claude/**))
interacts with user-global lookup is untested.

Environment

  • Claude Code: 2.1.133
  • OS: Linux x86_64

Impact

Users who store global rules, scripts, or skills under ~/.claude/ (a
documented and encouraged location) cannot allowlist edits to those files
without a permission prompt every time, despite writing rules that match
both the documented syntax and the gitignore-style spec.

View original on GitHub ↗

10 Comments

github-actions[bot] · 3 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/55230
  2. https://github.com/anthropics/claude-code/issues/40076
  3. https://github.com/anthropics/claude-code/issues/36884

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

hwaterer · 3 months ago

Quick follow-up on the "is untested" line in the Notes section: ran the reproducer in a directory with no project-level .claude/settings.local.json discoverable at all. The prompt fires identically (Update(~/.claude/rules/test-rule.md)). Project-level settings.local.json (presence, contents, or absence) is not the cause — the bug reproduces purely on user-global allow-rule handling.

hwaterer · 3 months ago

Reviewed the three duplicates the bot flagged:

  • #40076 is closely related — same bug class (user-global ** allow rules not matching paths outside cwd). That issue's reproducer uses Read(/mnt/c/MCP/**) on WSL, ours uses Edit(~/.claude/**/*.md) on native Linux. Different specific paths, almost certainly the same underlying bug. Worth flagging that the failure mode isn't WSL- or Windows-mount-specific — it reproduces on a documented standard location (~/.claude/) on plain Linux.
  • #55230 is a feature request asking for tilde expansion support in permission patterns, premised on tilde not currently working. The current docs claim tilde is supported, but our reproducer shows tilde rules under ~/.claude/ don't fire — which would be consistent with their premise that the expansion isn't actually happening at match time. May share a root cause.
  • #36884 is VS Code extension on Windows with project-level rules — different platform and scope, likely a separate bug.

Not closing as duplicate of any of the three: #40076 is the closest but ours adds a distinct reproducer (documented ~/.claude/ location, native Linux, Edit(...) and Update(...) scope-name forms both tested) that may help maintainers narrow root cause.

smanavi-lz · 3 months ago

Same loaded-but-unmatched symptom on different rule scopes (Claude Code 2.1.142, macOS).

Two examples from a single session, cwd is the project root (~/<project>/), which contains a nested sub-project (~/<project>/<sub-project>/) with its own .claude/.

1. Skill(...) ruleSkill(<skill-name>) is present in both ~/<project>/.claude/settings.local.json and the nested ~/<project>/<sub-project>/.claude/settings.local.json. /permissions → search "skill" confirms Skill(<skill-name>) is in the loaded allow list. Invoking the skill still fires a permission prompt.

2. mcp__... rulemcp__<internal_google_workspace_mcp>__drive_get_file_by_url is allowed in three settings files (user-global, shared project, nested project-local). /permissions → search "drive_get_file" confirms both mcp__<internal_google_workspace_mcp>__drive_get_file and mcp__<internal_google_workspace_mcp>__drive_get_file_by_url are in the loaded allow list. Calling the tool still fires a permission prompt offering "always allow."

Ruled out on my side:

  • All five settings files (user, user-local, shared project, project-local, nested project-local) parse as valid JSON.
  • No deny or ask entries; no defaultMode/permissionMode override.
  • Enterprise-managed file (/Library/Application Support/ClaudeCode/managed-settings.json) only denies destructive rm/git push --force — doesn't touch Skill(...) or mcp__... scopes.
  • Status panel confirms all setting sources are loaded: User settings, Shared project settings, Project local settings, Command line arguments, Enterprise managed settings (file).

Suggests the matcher regression isn't path-specific to ~/.claude/ — it also affects Skill(...) and mcp__... scope names.

Symptom started ~2 days before reporting, which aligns roughly with the 2.1.142 release window for this account.

hwaterer · 3 months ago

Another symptom on this thread: Read rules fail to match under subagent dispatch.

A subagent with Read in its tool whitelist, dispatched against a path matched by a user-level Read(~/.claude/**) allow rule, reports it could not read the file and falls back to whatever context was already loaded.

State verified:

  • Read(~/.claude/**) is in the user-global allow array.
  • /permissions shows it loaded.
  • No Read(...) deny entries in ~/.claude/settings.json.
  • The subagent's frontmatter explicitly grants Read.

So the allow rule should match. Empirically it doesn't. Surfaces as "denied" rather than "prompted" because a subagent has no UI to accept a permission prompt — an unmatched allow rule with no fallback escape becomes a hard fail in the subagent's reasoning.

Adds Read in subagent dispatch to the bug's surface (alongside Edit/Update for paths and Skill(...) / mcp__... for scope names per the 2026-05-15 comment). The 2026-05-08 verification deliberately left Read untested with the note that "reads have historically not prompted" — true for interactive sessions where the read-only tool tier auto-approves, but subagents go through the permission matcher on Read calls and an unmatched allow rule has no escape there.

hwaterer · 3 months ago

Adding a Linux-side data point that extends the surface further: project-relative /path rules in user-level ~/.claude/settings.json also load but don't fire at runtime. Bug persists at 2.1.143 (one patch after the 2.1.142 macOS report above).

Environment

  • Claude Code: 2.1.143
  • OS: Ubuntu 24.04.4 LTS (Linux 6.8.0-111-generic)

Setup

~/.claude/settings.json:

"permissions": {
  "allow": ["Edit(/covered-path/**)"]
}

(/covered-path/** is project-root-relative per the docs' single-slash convention.)

<project>/.claude/settings.local.json:

"permissions": { "defaultMode": "default" }

Two probe files in the project working tree:

  • <project>/uncovered-path/probe.txt — no allow rule covers it.
  • <project>/covered-path/probe.txt — covered by the user-level rule above.

Test 1 — fresh subagent (general-purpose, model: sonnet)

  • Edit on the covered path → DENIED ("Permission to use Edit has been denied.").
  • Parallel negative control on the uncovered path → also DENIED (expected; subagents can't satisfy interactive prompts, so both denying confirms the test surface is discriminating).

Test 2 — parent session

  • Edit on the uncovered path → permission prompt (expected; confirms defaultMode: default is genuinely active at runtime).
  • Edit on the covered path → permission prompt (NOT expected; the user-level rule should have matched).

Interpretation

The rule appears in /permissions as loaded; it doesn't fire at match time for an Edit against a path it should cover. Same shape as the original ~/.claude/** tilde-prefix failures and the 2026-05-15 Skill(...) / mcp__* scope-name failures — now also reproducible with project-relative /path rules in user-level settings, on Linux, at 2.1.143, for both fresh subagents and the parent session.

Consistent with the matcher regression sitting at a layer shared across multiple rule scope types rather than being specific to any one path-syntax or scope-name family.

Side note on test setup. Mid-session changes to defaultMode in project settings.local.json did not take effect in the live session — a Claude Code restart was needed before the test became discriminating. May or may not be relevant to the matcher bug; flagging in case it's a clue about how settings are read.

twbarnes1972 · 3 months ago

Same root cause as #36884: the XIq rule predicate in claude.exe's permission matcher hard-rejects any rule with ruleValue.ruleContent !== undefined, so all path-globbed Edit(...) / Read(...) / Write(...) and (per your May 17 comment) Skill(...) and mcp__...(...) rules fail to match for tools that don't have their own content matcher.

Full disassembly + reproducer matrix in that comment. Treating these two as duplicates is probably the right move.

hwaterer · 2 months ago

Still open and not confirmed resolved — commenting to prevent stale auto-closure. The root-cause analysis (permission-matcher disassembly) is on #36884, which this appears to duplicate; flagging that #57132 carries the accurate, surface-agnostic title, whereas #36884 reads as VS-Code-specific despite the bug reproducing cross-platform (macOS, Windows, Linux).

twbarnes1972 · 1 month ago

Following up: this appears fixed in current Claude Code. I re-ran the reproducer from my
earlier comment against the native CLI (claude.exe 2.1.207, Windows) in an isolated config
sandbox, and path-globbed allow rules under ~/.claude/ now match at runtime — an
Edit(<glob>) allow that previously showed as loaded but never matched is now honored
(allowed, no prompt), with a no-rule control still correctly blocked. Path-globbed deny rules
also take effect now.

I don't know exactly which release fixed it — it landed somewhere between 2.1.145 and 2.1.207
with no changelog note I could find — so if you're on an older build an upgrade is the fix.
For reference, the underlying matcher change appears to be an added content-matching path
rather than removal of the old reject filter (the original ruleContent-reject line is still
in the 2.1.207 binary), which is consistent with the behavior now working while the old code
path remains.

Verification was native-CLI only; I didn't test the VS Code extension surface.

hwaterer · 1 month ago

Confirmed on Linux, 2.1.207. The //-absolute allow rules fire, but ~/-tilde ones still don't. The tilde form is this issue's own reproducer, so #57132 as filed still reproduces. The residual looks like ~ not being expanded on the allow path (#55230), as opposed to the #36884 ruleContent reject, which appears fixed for absolute globs.

Showing cached comments. Read the full discussion on GitHub ↗