[BUG] Hooks with `shell: "powershell"` spawn `pwsh` with no `powershell.exe` fallback, so hooks silently never run on a stock Windows box
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
What's Wrong?
On a Windows machine with no PowerShell 7 (pwsh) and no Git Bash, hooks never execute. There is no error surfaced in the session, and the tool call proceeds as if the hook had allowed it.
Both available shell values are dead on such a machine:
shell: "powershell"spawnspwsh— PowerShell 7, a separate product that is not present on a stock Windows install. Windows ships Windows PowerShell 5.1 aspowershell.exe, which is never tried as a fallback for hooks.shell: "bash"/ omitted resolves to/bin/sh, which does not exist on Windows unless Git Bash provides it.
The shell field is a closed enum (bash | powershell), and no setting or environment variable can point it at powershell.exe or an arbitrary interpreter, so on this configuration there is no valid value for it.
Why this matters beyond convenience. A PreToolUse hook blocks only by exiting exactly 2; every other outcome is treated as a non-blocking hook error and the tool call runs. A hook whose interpreter cannot be launched never returns anything, so it cannot return 2 — it fails open, and nothing in the session says the hook did not run. Any hook used as a guard or policy gate is silently inert on these machines, while appearing correctly configured in settings.json.
It is also confusing rather than merely broken, because the agent's own PowerShell tool keeps working on the same machine: the terminal/tool resolution path does have a wt.exe → pwsh.exe → powershell.exe → cmd fallback chain. So PowerShell commands execute normally while the hook meant to gate them cannot start.
What Should Happen?
Any one of these would resolve it:
- Fall back to
powershell.exewhenpwshis unavailable, matching the fallback the terminal/tool path already implements. - Allow an explicit interpreter — either a third enum value for Windows PowerShell 5.1, or an absolute path in
shell(there is aCLAUDE_CODE_GIT_BASH_PATHprecedent for locating bash). - Surface the failure. If no interpreter is available, report it — a visible error, or treating an unlaunchable hook on a blockable event as a block rather than a pass. Silent fail-open is the most damaging part.
Error Messages/Logs
Nothing is surfaced in the session — that is the core of the report.
The hook is present in settings.json, the tool call proceeds, and no error,
warning, or hook-failure notice appears.
Steps to Reproduce
- Use a Windows machine with neither PowerShell 7 nor Git Bash installed. Confirm:
``powershell``
where pwsh # not found
Test-Path "C:\Program Files\Git\bin\bash.exe" # False
$PSVersionTable.PSVersion # 5.1.x — Windows PowerShell only
- Add a
PreToolUsehook to%USERPROFILE%\.claude\settings.jsonthat always blocks:
``json``
{
"hooks": {
"PreToolUse": [
{
"matcher": "PowerShell",
"hooks": [
{ "type": "command", "shell": "powershell", "command": "exit 2" }
]
}
]
}
}
- Start Claude Code and have it run any PowerShell command, e.g.
Start-Sleep -Seconds 1. - Observed: the command runs. The hook never executed and nothing reports that.
Expected: the command is blocked (exit 2), or a visible error explains that the hook's interpreter is unavailable.
- Repeat with
"shell": "bash"and with theshellkey omitted — same result, since that path resolves to/bin/sh.
Note the same machine can still run PowerShell through the agent's PowerShell tool, which is what makes this easy to miss.
Is this a regression?
I don't know
Claude Code Version
2.1.247 (Claude Code)
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
PowerShell
Additional Information
Reproduced on Windows 11 10.0.26100, ARM64, with Claude Code 2.1.247 — both the standalone CLI (%USERPROFILE%\.local\bin\claude.exe) and the VS Code extension's bundled engine (anthropic.claude-code-2.1.247-win32-arm64\resources\native-binary\claude.exe). Both read the same %USERPROFILE%\.claude\settings.json, and both behave identically.
The two resolution paths appear to differ in the shipped bundle. The hook/headless execution path selects the interpreter unconditionally:
{file: "pwsh", args: ["-NoProfile", "-Command", cmd]} // shell === "powershell"
{file: "/bin/sh", args: ["-c", cmd]} // otherwise
while the terminal/tool path does implement a fallback chain:
wt.exe → pwsh.exe → powershell.exe → process.env.ComSpec (cmd)
If that fallback were applied to the hook path, this configuration would work.
The shipped claude-code-settings.schema.json documents the current behaviour, and it is accurate — "'powershell' uses pwsh". The request is to change the behaviour (or make the failure visible), not the documentation.
Related but distinct: #5049 (general Windows shell awareness), #7490 (configuring the Bash tool's shell), #85475 (hook paths and App Execution Aliases). None cover hook interpreter resolution or the silent fail-open.