[BUG] Windows: ${CLAUDE_PLUGIN_ROOT} left unexpanded in Claude Desktop (Cowork), PreToolUse Bash hook fully blocked
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Windows: plugin hook ${CLAUDE_PLUGIN_ROOT} left unsubstituted in
Claude Desktop (Cowork), breaking PreToolUse Bash hooks
Related: #39550 (canonical, closed, macOS), #43380, #18527 — this is the Windows manifestation with blocking (non-cosmetic) severity
Environment:
- Claude Desktop (Cowork), latest, Windows 11
- Python 3.14
- Org-managed (remote) plugins with PreToolUse Bash hooks
Symptom:
Running a Bash tool call fails. The hook command is shell form:
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/check-bash.py"
and produces:
python.exe: can't open file
'...\outputs\${CLAUDE_PLUGIN_ROOT}\hooks\scripts\check-bash.py':
[Errno 2] No such file or directory
Root cause:
On Windows, ${CLAUDE_PLUGIN_ROOT} is NOT substituted before the hook
subprocess is spawned. The literal string "${CLAUDE_PLUGIN_ROOT}" is
passed through and resolved relative to the session cwd, so the file
is never found. This matches #43380 (CLAUDE_PLUGIN_ROOT not injected
at hook execution time) and #11984 (portability). Related backslash-
handling variants on Windows: #18527, #22449, #23204.
Request:
Substitute ${CLAUDE_PLUGIN_ROOT} as a plain string into command/args
before spawning on Windows (as documented for exec form), and ensure
the path is correct for the target shell.
Note: a .sh-based hook in the same plugin also fails on Windows
(cannot be spawned directly); cf. #21847.
What Should Happen?
Expected Behavior:
${CLAUDE_PLUGIN_ROOT} should be substituted to the plugin's actual
installation directory before the hook subprocess is spawned on
Windows — the same as on macOS/Linux — so the hook script is found
and runs.
Concretely:
- The literal string "${CLAUDE_PLUGIN_ROOT}" must never reach the
spawned process unexpanded.
- The substituted path must be in a form the target interpreter/shell
can resolve on Windows (handle backslashes and spaces in the path).
- This should hold for both shell form and exec form, and regardless
of the interpreter (python3, node, bash, etc.).
- Result: the PreToolUse Bash hook runs without error and the Bash
tool is usable on Windows.
Error Messages/Logs
PreToolUse:mcp__workspace__bash hook error:
[python3 ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/check-bash.py]:
C:\Users\...\AppData\Local\Python\pythoncore-3.14-64\python.exe:
can't open file
'C:\Users\...\AppData\Roaming\Claude\local-agent-mode-sessions\
...\outputs\${CLAUDE_PLUGIN_ROOT}\hooks\scripts\check-bash.py':
[Errno 2] No such file or directory
Steps to Reproduce
Steps to Reproduce:
Environment: Windows 11, Claude Desktop (Cowork), latest.
- Create a minimal plugin with a PreToolUse Bash hook in SHELL form
(no "args"), referencing the placeholder. hooks/hooks.json:
{
"hooks": {
"PreToolUse": [
{ "matcher": "Bash",
"hooks": [
{ "type": "command",
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/scripts/check-bash.py" }
] }
]
}
}
- Add the referenced script so the path is valid when expanded:
hooks/scripts/check-bash.py -> print("ok")
- Install/enable the plugin in Claude Desktop (Cowork) on Windows.
- In a session, trigger any Bash tool call.
Observed:
python.exe: can't open file
'...\outputs\${CLAUDE_PLUGIN_ROOT}\hooks\scripts\check-bash.py':
[Errno 2] No such file or directory
-> "${CLAUDE_PLUGIN_ROOT}" is left literal and resolved relative to
the session cwd; the file is never found and the Bash tool is blocked.
Notes:
- Same hooks.json works on macOS/Linux.
- Reproduces regardless of whether the user profile path contains a
space (a space makes it worse; cf. path tokenization).
Claude Model
None
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
Claude 1.11187.1 (370d3b) 2026-06-04T17:30:35.000Z
Platform
Other
Operating System
Windows
Terminal/Shell
PowerShell
Additional Information
_No response_
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗