ai newsMo., 2. Nov.
EilmeldungAmazon ECS Gateway Arrives with Generated Hostnames and No Custom Domain Support

Claude Code hooks on Windows: when a Bash matcher sees nothing

A reader spotted a Windows gap in my earlier article about blocking destructive shell commands in Claude Code. The hook matched Bash . On a machine where Claude uses its PowerShell tool, that hook can miss the command entirely. The previous article now includes the correction . This follow-up expla

dev.toMontag 10:19 · 02.11.4 Min. Lesezeit
Bild: dev.to

A reader spotted a Windows gap in my earlier article about blocking destructive shell commands in Claude Code. The hook matched Bash. On a machine where Claude uses its PowerShell tool, that hook can miss the command entirely.

The previous article now includes the correction. This follow-up explains the two configuration issues involved: which tool triggers the hook, and what happens if the hook cannot run. There is also a separate limitation: the original guard.py understands POSIX-style commands, so PowerShell commands remain outside its coverage.

Claude Code has separate Bash and PowerShell tools. Matching one tool does not automatically match the other.

The PowerShell section of the hooks documentation states: “A hook that matches only Bash never fires there.” The surrounding explanation concerns Windows without Git Bash, where PowerShell is enabled automatically and Bash is not registered.

The issue also matters wherever the PowerShell tool is enabled on Windows: Claude treats it as the primary shell and routes shell commands through it. The tools reference explains availability by platform and account. With Git Bash installed, Bash can remain available for POSIX scripts.

The practical correction is to match both tool names with Bash|PowerShell. Both supply the command string through tool_input.command, according to the tools reference.

That fixes event routing. It does not establish that a script understands both command languages.

A second gap appears when the hook is selected but cannot execute. Perhaps its Python executable is missing, its script path is wrong, or the process crashes.

The failure-handling documentation says actions generally continue when hooks fail or time out. An unavailable guard can therefore leave an action proceeding through the normal permission flow.

For a command hook, setting onFailure to block makes a failure block the triggering action according to that event's blocking behavior. For PreToolUse, that means blocking the tool call. The documentation specifies: “Requires Claude Code v2.1.295 or later.”

There is a Windows qualification: without Git Bash, a shell-form PreToolUse or PermissionRequest hook can block on failure when onFailure is unset. That documented exception is why describing every missing-interpreter failure as non-blocking would be inaccurate. Set the failure policy explicitly instead of depending on that default.

For an existing guard saved at.claude/hooks/guard.py, this illustrates the configuration in.claude/settings.json:

{ "hooks": { "PreToolUse": [ { "matcher": "Bash|PowerShell", "hooks": [ { "type": "command", "command": "python.exe", "args": [ "${CLAUDE_PROJECT_DIR}/.claude/hooks/guard.py" ], "onFailure": "block" } ] } ] } } Enter fullscreen mode Exit fullscreen mode Use the executable actually installed on your machine. python.exe here is an example, not a claim that Python is installed or discoverable on your PATH.

The args array selects exec form. As described in the command-hook documentation, Claude Code starts the executable directly and passes each argument separately. The script path therefore stays one argument even when the project directory contains spaces. On Windows, exec form requires a real executable.

This example is a configuration illustration checked against the documentation; I have not tested it on your Windows installation.

The original guard.py only understands POSIX-style command syntax. Adding PowerShell to the matcher does not teach it PowerShell grammar, aliases, parameter binding, or command composition.

The wider matcher delivers PowerShell events to the script, but its existing analysis still leaves those commands uncovered. A successful exit from that script is not evidence that it evaluated a PowerShell command correctly.

Likewise, onFailure: block covers hook failures. It cannot repair a parser that runs successfully but misunderstands its input or returns no blocking decision. PowerShell coverage requires an appropriate implementation and separate verification. This settings change does not provide it.

The shell launching a hook and the tool being inspected are also separate choices. Running the hook under PowerShell does not change the language of the command received in the event.

Before relying on this setup, check that a harmless call through each available shell tool actually reaches the hook. Confirm the received tool name and command field rather than inferring coverage from the settings file.

Then exercise failure handling safely: temporarily point the hook at a missing script and request a harmless listing. On a supported version with onFailure: block, the call should be blocked. Restore the path afterward.

Finally, test the guard's actual decisions separately for each supported language. Until PowerShell analysis exists and passes those checks, describe this as a POSIX guard with corrected routing and failure handling.

If your team wants this set up and tested on your own machines, I offer it as a fixed-price package on Upwork: https://www.upwork.com/services/product/2115796250869866384

Quelle: dev.to (dev.to). Original öffnen