[BUG] Write permission allowlist ignores relative file_path that resolves to absolute-path rule target (still broken in 2.1.122 — see #29098 / #37220 / #38391)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet (filing fresh per the auto-lock policy noted in #38391 — see references below)
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
A Write permission rule for an absolute path — e.g. Write(/tmp/scratch-*.mjs) — does not match a Write tool call whose file_path argument is a relative string that resolves to the same file, e.g. ../../../../tmp/scratch-abc.mjs. The permission matcher compares the literal file_path string against the rule pattern instead of canonicalizing to an absolute path first. As a result, the user is prompted to approve the write despite a correctly-configured allow rule.
This behavior makes it effectively impossible for a custom skill author to pre-authorize a scratch directory: the skill author cannot enforce how the model spells the path when it calls Write from an arbitrary cwd.
This was previously reported in #29098 (\"Permission patterns don't match /tmp/ paths when resolved to relative\"), which was closed as stale on 2026-03-27, and in #37220 and #38391 (all closed). The bug is still present on the current version (2.1.122), now also hitting the new skills surface (allowed-tools frontmatter does not protect against it either). Filing a fresh report per the auto-lock policy noted in #38391.
What Should Happen?
A Write call with file_path: \"../../../../tmp/scratch-abc.mjs\", from any cwd where that resolves to /tmp/scratch-abc.mjs, should match the rule Write(/tmp/scratch-*.mjs) and proceed silently. Matching on absolute-path rules should be done against the canonicalized target, not the literal argument string.
For comparison (also noted in #38391): Read rules with absolute-path patterns appear to work correctly. Only Write is affected.
Error Messages/Logs
Create file
../../../../tmp/scratch-abc123.mjs
Do you want to create scratch-abc123.mjs?
1. Yes
2. Yes, allow all edits in tmp/ during this session
3. No
(No error — the prompt itself is the unexpected behavior. Note the displayed path is the literal relative string, confirming the matcher sees ../../../../tmp/... rather than the resolved /tmp/....)
Steps to Reproduce
- Create
~/.claude/skills/repro-skill/SKILL.md:
``yamlconsole.log(\"hi\")
---
name: repro-skill
description: Writes a scratch file to /tmp/ and runs it.
allowed-tools: Write(/tmp/scratch-*.mjs), Bash(node /tmp/scratch-*.mjs)
---
When invoked, write a short JS file to /tmp/scratch-<id>.mjs containing and then run it with node.``
- Ensure
~/.claude/settings.jsonhas the same rules inpermissions.allow:
``json``
{
\"permissions\": {
\"allow\": [
\"Write(/tmp/scratch-*.mjs)\",
\"Bash(node /tmp/scratch-*.mjs)\"
]
}
}
- Start a fresh Claude Code session in a project directory several levels deep, e.g.
~/Desktop/gitrepos/some-project/. - Ask Claude: \"use the repro-skill to write and run a scratch file.\"
- Observed: a prompt appears:
Create file ../../../../tmp/scratch-<id>.mjs — Do you want to create scratch-<id>.mjs? - Expected: the file is created and executed silently, since both rules match the intended absolute target.
Additional variants tested — all still prompt:
Write(//tmp/scratch-*.mjs)(double-slash form)Write(//private/tmp/scratch-*.mjs)(symlink-resolved form —/tmpis a symlink to/private/tmpon macOS)Write(/private/tmp/scratch-*.mjs)Write(**/scratch-*.mjs)(broad glob)- Same patterns placed in the SKILL.md
allowed-toolsfrontmatter
Only manually accepting the prompt (option 1 or 2) or using a PreToolUse hook workaround (as shown in #38391) unblocks the write.
Claude Model
Opus
Is this a regression?
I don't know
Claude Code Version
2.1.122 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
- Related closed issues reporting the same root cause: #29098, #37220, #38391, and #49378 (the last was closed as a duplicate of #37939, which is about
.claude/writes specifically — a different surface but the same underlying relative-vs-absolute matcher issue). - This report adds the skills surface as a new angle: the SKILL.md
allowed-toolsfrontmatter also does not resolve the bug. Skill authors who want a \"write-a-script-and-run-it\" skill to operate without user intervention currently have no reliable pre-authorization path. - Suggested fix direction (from #38391, still applies): canonicalize
file_pathviarealpathbefore matching against absolute-path allow rules. This would alignWriterule matching withReadrule behavior.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗