anthropics / anthropics/claude-code
[BUG] Windows: path-guard hook shared with Linux denies every Edit/Write, even on its own settings file (tool_input.file_path is D:\... but hook shell $PWD/$HOME are POSIX /d/...)
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
### Environment
- Claude Code 2.1.270, native Windows 10 Pro (10.0.19045); hook commands run in Git Bash
- The same project is also used on Ubuntu. The folder is synced between the machines, so `.claude/settings.json` (permissions + hooks) is shared.
### What's wrong
The project settings contain a PreToolUse guard for `Edit|Write|NotebookEdit` that only allows files inside the project, the memory dir and the scratchpad (deny message abridged):
```json
"command": "jq -r '.tool_input.file_path // .tool_input.notebook_path // empty' | { read -r f; case \"$f\" in \"$PWD\"/*|\"$HOME\"/.claude/*|/tmp/claude-*) exit 0;; *) printf '%s' '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"BLOCKED: file outside the project\"}}';; esac; }"
```
It works on Linux. On Windows it denies **every** Edit/Write, because the two sides of the comparison use different path formats:
| value | on Windows |
|---|---|
| `tool_input.file_path` | `D:\Dropbox\project\doc.tex` |
| `$PWD` in the hook shell (Git Bash) | `/d/Dropbox/project` |
| memory file vs `$HOME` | `C:\Users\\.claude\projects\...` vs `/c/Users/` |
| scratchpad | `C:\Users\\AppData\Local\Temp\claude\...` (Git Bash sees `/tmp/claude/...`, not `/tmp/claude-*`) |
Consequences:
1. The guard fails **closed**: every file inside the project is reported as "outside the project".
2. It also denies edits to `.claude/settings.json` itself, so the session cannot repair it. Claude is locked out, and the user has to edit the JSON by hand (or explicitly authorize a shell write that bypasses the Edit/Write diff prompt).
3. The only feedback is the hook's own deny message; nothing points to a path-format mismatch, so it looks like a permissions failure.
Because the settings are shared across machines, path rules can only be portable if they are relative to the project, and a hook is the natural place to express that. On Windows, however, the hook sees paths in a different format than its own shell.
### Related
- #83877 (closed, docs): documents that `file_path` is backslash-separated on Windows and mentions the `$PWD` trap as a fail-*open* case. The mismatch itself remains; with allow-list guards it fails closed and self-locks.
- #76490, #64432: permission rules not matching Windows paths, i.e. the same missing path normalization on Windows.
### What should happen
A settings file that works on Linux/macOS should not silently change meaning on Windows. For example:
- pass hooks paths in the format of the shell that runs them (POSIX under Git Bash), or add normalized fields next to the raw ones (e.g. `file_path_posix`, `cwd_posix`);
- expose the project and scratchpad dirs (e.g. `CLAUDE_PROJECT_DIR`) in a form directly comparable with `tool_input.file_path`;
- offer a recovery path (e.g. from `/hooks`) when a hook blocks edits to the settings file that defines it.
### Steps to reproduce
1. Native Windows, Git Bash as hook shell.
2. Put the hook above in `/.claude/settings.json`.
3. Ask Claude to edit any file in the project: denied ("outside the project").
4. Ask Claude to fix `.claude/settings.json`: denied as well.
### Workaround
Normalize both sides with `cygpath` (on Linux it does not exist and the path is used as is):
```sh
f=$(jq -r '.tool_input.file_path // .tool_input.notebook_path // empty')
u() { cygpath -u "$1" 2>/dev/null || printf '%s' "$1"; }
f=$(u "$f"); p=$(u "${CLAUDE_PROJECT_DIR:-$PWD}")
case "$f" in "$p"/*|"$HOME"/.claude/*|/tmp/claude-*|/tmp/claude/*|/c/Users/*/AppData/Local/Temp/claude/*) exit 0;; *) printf '%s' '';; esac
```
Checked on Windows: project, memory and scratchpad paths are allowed; `D:\Dropbox\other\x.tex` and `D:\Dropbox\project-other\x.tex` are denied; POSIX-style paths behave the same.
Contributor guide
No contributing guide indexed for this repository
Research direction
Reproduce with the PreToolUse command in /.claude/settings.json under native Windows with Git Bash, then compare the Windows tool_input paths with the hook's $PWD/$HOME values. Check the /hooks recovery path described in the issue. Done means shared settings allow project, memory, and scratchpad edits while denying the two out-of-scope examples on Windows and Linux without locking edits to settings.json.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bash
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100