[BUG] Windows: Claude Code never executes configured hook commands — hook runner invokes `git` instead and silently swallows the failure
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?
Environment
- Claude Code version: 2.1.197
- Affected entrypoints: Both reproduce identically
- Desktop app for Windows (
entrypoint: "claude-desktop"), installed via WinGet (Anthropic.Claude) - Standalone CLI (
entrypoint: "cli"), installed via the official native installer (irm https://claude.ai/install.ps1 | iex) to~/.local/bin/claude.exe - OS: Windows 11 Home, build 10.0.26200 (x64)
- Git: 2.53.0.windows.1, standard install at
C:\Program Files\Git(bothbin\bash.exeandusr\bin\bash.exepresent at their expected locations — this is a normal, unmodified Git for Windows layout, not a broken/nonstandard install) - Shell used to launch Claude Code: Windows PowerShell 5.1
Summary
On this Windows machine, no hook configured in ~/.claude/settings.json ever actually executes — regardless of the hook event, the command content, or whether the command is wrapped in cmd /c "..." or given as a bare path. Claude Code's own session transcript confirms it attempts to run the hook (the event fires and is logged), but the process that actually runs is not the configured command: it appears to invoke plain git instead, which prints git's usage/help banner and exits with code 1. Claude Code classifies this as a "non-blocking" hook error and silently proceeds, so the user sees no indication anything is wrong — the hook simply never has any effect.
This makes every hook-based integration on Windows non-functional (tested with a third-party governance tool, but the minimal repro below has nothing to do with that tool — it's a single trivial batch file).
Steps to reproduce (minimal repro)
- Create a trivial batch file, e.g.
C:\Users\<you>\probe.cmd:
``bat``
@echo off
echo PROBE-FIRED %DATE% %TIME% >> "%USERPROFILE%\probe.log"
- In
~/.claude/settings.json, configure it as the only hook:
``json`
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{ "type": "command", "command": "C:\\Users\\<you>\\probe.cmd" }
]
}
]
}
}
"command": "cmd /c \"C:\\Users\\<you>\\probe.cmd\""` — identical result.)
(Also tested wrapped as
- Delete
probe.logif it exists. - Start a brand-new Claude Code session (not resumed) — reproduced in both the Desktop app and
claudeCLI. - Send any prompt, e.g. "hello".
- Check
%USERPROFILE%\probe.log.
Expected behavior
probe.log gets a new line each time a prompt is submitted, since the batch file does nothing but append to it.
Actual behavior
probe.log is never created. The session's transcript JSONL (~/.claude/projects/.../<session-id>.jsonl) contains a hook_non_blocking_error attachment for the UserPromptSubmit event whose command field correctly matches the configured hook, but whose stdout is git's bare usage banner and exitCode is 1 — i.e., the configured command was never actually run; something else (apparently git with no/invalid arguments) ran instead.
Example attachment from a real CLI session (entrypoint: "cli", sessionId redacted), reproduced verbatim except for truncating the (unmodified) git usage text for brevity:
{
"attachment": {
"type": "hook_non_blocking_error",
"hookName": "UserPromptSubmit",
"hookEvent": "UserPromptSubmit",
"stderr": "Failed with non-blocking status code: No stderr output",
"stdout": "usage: git [-v | --version] [-h | --help] [-C <path>] [-c <name>=<value>]\n [--exec-path[=<path>]] ... (full git top-level usage banner) ...",
"exitCode": 1,
"command": "C:\\Users\\<you>\\probe.cmd",
"durationMs": 381
},
"type": "attachment",
"entrypoint": "cli",
"cwd": "C:\\Users\\<you>\\<project>",
"version": "2.1.197"
}
The identical attachment (same stdout, exitCode: 1) was produced in a separate test run through the Desktop app (entrypoint: "claude-desktop"), and in a third run with the hook command written as cmd /c "..." instead of a bare path — ruling out both the entrypoint and the command-quoting style as the variable. Only one hook was configured in each test (all others removed), so there is no ambiguity about which hook produced this result.
What Should Happen?
What we ruled out
- Not a broken/nonstandard Git for Windows install —
git --version,git.exelocation, and bothbin\bash.exe/usr\bin\bash.exeare present exactly where Git for Windows normally puts them. - Not a
settings.jsonsyntax problem — the JSON is valid (parses fine), and the error attachment's owncommandfield proves Claude Code read the correct, intended command string. - Not specific to the Desktop app — reproduced identically via the standalone CLI (
entrypoint: "cli"), installed separately via the official native installer. - Not specific to
cmd /cquoting — reproduced identically with a bare, unwrapped path as thecommandvalue. - Not a fresh-session/caching issue — reproduced across multiple brand-new sessions (not resumed) in both interfaces.
- Not related to a UTF-8 BOM in hook stdin (a separate, real issue we found and worked around upstream of this one, in a third-party tool's own hook gateway) — this repro is a plain
.cmdbatch file with no JSON parsing involved at all, so BOM handling is irrelevant here.
Impact
Because the failure is classified as "non-blocking" and produces no visible error to the user, any hook-based tool on Windows silently does nothing, while appearing (from settings.json) to be correctly configured. For security/governance tooling built on Claude Code hooks specifically, this means enforcement silently never happens — a user could reasonably believe a hook-based guardrail is active when it is not.
Error Messages/Logs
Steps to Reproduce
Suggested next steps for triage
- Check the Windows-specific hook command execution path for anything that shells out to
git(e.g., as part of Git Bash detection/selection logic for the Bash tool) that might be getting invoked in place of the actual hookcommand, or that the hookcommandis being incorrectly appended as an argument to agitinvocation. - A
claude doctoror verbose/debug flag that surfaces the exact argv/CreateProcess call Claude Code makes for a hook on Windows would make this much faster to diagnose further.
Happy to provide the full (unredacted) transcript JSONL lines, settings.json, or run further isolation tests on request.
Claude Model
Sonnet (default)
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.197
Claude Code Version
2.1.197
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
PowerShell
Additional Information
_No response_