{"slug": "claude-code-hooks-on-windows-when-a-bash-matcher-sees-nothing", "title": "Claude Code hooks on Windows: when a Bash matcher sees nothing", "summary": "A developer documented two configuration gaps in Claude Code's hook system on Windows: a hook matching only the Bash tool never fires when Claude routes commands through its PowerShell tool, and a hook that fails to execute can let the guarded action proceed. The fix is to match both tool names with \"Bash|PowerShell\" and set onFailure to \"block\" in .claude/settings.json, which requires Claude Code v2.1.295 or later. The developer noted the example configuration was checked against documentation but not tested on other Windows installations.", "body_md": "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.\n\n[The previous article now includes the correction](https://dev.to/martinpisacreta/how-i-stop-claude-code-from-running-destructive-shell-commands-5c98). 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.\n\nClaude Code has separate Bash and PowerShell tools. Matching one tool does not automatically match the other.\n\nThe [PowerShell section of the hooks documentation](https://code.claude.com/docs/en/hooks.md#powershell) 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.\n\nThe 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](https://code.claude.com/docs/en/tools-reference.md#powershell-tool) explains availability by platform and account. With Git Bash installed, Bash can remain available for POSIX scripts.\n\nThe 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.\n\nThat fixes event routing. It does not establish that a script understands both command languages.\n\nA 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.\n\nThe [failure-handling documentation](https://code.claude.com/docs/en/hooks.md#block-the-action-when-a-hook-fails) says actions generally continue when hooks fail or time out. An unavailable guard can therefore leave an action proceeding through the normal permission flow.\n\nFor 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.”\n\nThere 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.\n\nFor an existing guard saved at `.claude/hooks/guard.py`, this illustrates the configuration in `.claude/settings.json`:\n\n```\n{\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \"Bash|PowerShell\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"python.exe\",\n            \"args\": [\n              \"${CLAUDE_PROJECT_DIR}/.claude/hooks/guard.py\"\n            ],\n            \"onFailure\": \"block\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nUse 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.\n\nThe `args` array selects exec form. As described in the [command-hook documentation](https://code.claude.com/docs/en/hooks.md#exec-form-and-shell-form), 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.\n\nThis example is a configuration illustration checked against the documentation; I have not tested it on your Windows installation.\n\nThe 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.\n\nThe 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.\n\nLikewise, `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.\n\nThe 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.\n\nBefore 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.\n\nThen 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.\n\nFinally, 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.\n\nIf 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](https://www.upwork.com/services/product/2115796250869866384)", "url": "https://wpnews.pro/news/claude-code-hooks-on-windows-when-a-bash-matcher-sees-nothing", "canonical_source": "https://dev.to/martinpisacreta/claude-code-hooks-on-windows-when-a-bash-matcher-sees-nothing-30l4", "published_at": "2026-11-02 09:19:01+00:00", "updated_at": "2026-11-02 09:24:17.413398+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools"], "entities": ["Claude Code", "Anthropic", "PowerShell", "Bash", "Windows"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/claude-code-hooks-on-windows-when-a-bash-matcher-sees-nothing", "markdown": "https://wpnews.pro/news/claude-code-hooks-on-windows-when-a-bash-matcher-sees-nothing.md", "text": "https://wpnews.pro/news/claude-code-hooks-on-windows-when-a-bash-matcher-sees-nothing.txt", "jsonld": "https://wpnews.pro/news/claude-code-hooks-on-windows-when-a-bash-matcher-sees-nothing.jsonld"}}