[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)

Resolved 💬 4 comments Opened Apr 28, 2026 by charliesantos Closed May 31, 2026

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

  1. Create ~/.claude/skills/repro-skill/SKILL.md:

``yaml
---
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
console.log(\"hi\") and then run it with node.
``

  1. Ensure ~/.claude/settings.json has the same rules in permissions.allow:

``json
{
\"permissions\": {
\"allow\": [
\"Write(/tmp/scratch-*.mjs)\",
\"Bash(node /tmp/scratch-*.mjs)\"
]
}
}
``

  1. Start a fresh Claude Code session in a project directory several levels deep, e.g. ~/Desktop/gitrepos/some-project/.
  2. Ask Claude: \"use the repro-skill to write and run a scratch file.\"
  3. Observed: a prompt appears: Create file ../../../../tmp/scratch-<id>.mjs — Do you want to create scratch-<id>.mjs?
  4. 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 — /tmp is a symlink to /private/tmp on macOS)
  • Write(/private/tmp/scratch-*.mjs)
  • Write(**/scratch-*.mjs) (broad glob)
  • Same patterns placed in the SKILL.md allowed-tools frontmatter

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-tools frontmatter 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_path via realpath before matching against absolute-path allow rules. This would align Write rule matching with Read rule behavior.

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗