Permission glob matching: absolute path (/Users/...) fails when Claude Code normalizes to ~ internally
Summary
Permissions set with absolute paths in settings.json silently fail to match when Claude Code normalizes paths to ~ form internally before comparison.
Environment
- Claude Code version: (latest as of 2026-03-26)
- OS: macOS
- Permission entry location:
~/.claude/settings.json(global user settings)
Steps to Reproduce
- Add a Write permission using an absolute path in
~/.claude/settings.json:
``json``
"permissions": {
"allow": [
"Write(/Users/heng/.claude/users/**)"
]
}
- Trigger a Claude Code session (particularly a remote trigger / scheduled agent / heartbeat) that attempts to write to a path like
/Users/heng/.claude/users/ou_xxx/daily/2026-03-26.md - Claude Code prompts for permission despite the glob pattern being set
Verification
Node.js path.matchesGlob('/Users/heng/.claude/users/ou_xxx/daily/2026-03-26.md', '/Users/heng/.claude/users/**') returns true — the pattern is syntactically correct and matches the target path.
Root Cause (Suspected)
Claude Code appears to normalize permission patterns (or target paths) to ~-prefixed form before comparison. If the stored permission uses an absolute path (/Users/heng/...) but the comparison is done against ~/.claude/..., the strings don't match and the permission check falls through to a prompt.
Fix / Workaround
Adding both the ~ form and the absolute path form resolves the issue:
"permissions": {
"allow": [
"Write(/Users/heng/.claude/users/**)",
"Write(~/.claude/users/**)"
]
}
After adding the ~-prefixed variants, the next write to the same path succeeded without a prompt.
Expected Behavior
Write(/Users/heng/.claude/users/**) and Write(~/.claude/users/**) should be treated as equivalent since they resolve to the same path. The permission check should normalize both to a canonical form before comparison — not match only one form.
Impact
This is particularly problematic for scheduled agents / remote triggers (e.g. heartbeat tasks), because:
- They run in a potentially different process context
- The user is not present to approve the prompt interactively
- The permission was intentionally set to allow automated writes, but silently fails
Users who only use interactive sessions may not encounter this because they can approve the one-time prompt — which Claude Code then remembers. Automated/headless contexts have no such fallback.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗