Summary
On Windows, sqz init writes the Claude Code PreToolUse hook into settings.json using a backslash Windows path to the executable (C:\Users\...\sqz.exe). Claude Code executes hook commands through Git Bash (/usr/bin/bash), where \ is the escape character, so the path collapses and the hook fails on every Bash tool call:
PreToolUse:Bash hook error
Failed with non-blocking status code:
/usr/bin/bash: line 1: C:UsersUSERNAMEAppDataLocalProgramssqzbinsqz.exe: command not found
The failure is non-blocking, so commands still run — but the hook never executes, so no output compression happens, and the user sees the error on every command.
Relationship to #2 (this is a distinct, follow-on bug)
#2 ("sqz init doesn't escape \ in .claude/settings.local.json") was about the generated JSON being invalid — unescaped backslashes broke JSON parsing so Claude couldn't read the file. That has been fixed: the file now contains correctly-escaped C:\\Users\\...\\sqz.exe, which is valid JSON.
This issue is one layer down. The JSON now parses fine, but the path value it parses to (C:\Users\...\sqz.exe) is a Windows backslash path, and Claude Code executes hook commands through Git Bash — where backslashes are shell escapes. So the very escaping fix from #2 is what surfaces this: valid JSON, but a command string bash can't run. #2 fixed the JSON layer; this is the shell-execution layer.
Environment
- OS: Windows 11
- Shell Claude Code uses for tools/hooks: Git Bash (
MINGW64_NT, bash 5.3.x) — confirmed by the /usr/bin/bash: line 1: prefix in the error
- sqz: 1.0.9 (prebuilt binary)
- Installed via:
sqz init --global
Root cause
In sqz/src/main.rs, the executable path is taken straight from current_exe() with no separator normalisation:
let sqz_path = std::env::current_exe()
.map(|p| p.to_string_lossy().to_string()) // Windows => "C:\Users\...\sqz.exe"
.unwrap_or_else(|_| "sqz".to_string());
That sqz_path is then passed to sqz_engine::generate_hook_configs(&sqz_path), which emits the Claude Code hook command verbatim, e.g.:
"command": "C:\\Users\\...\\sqz.exe hook claude"
Claude Code runs that command via Git Bash on Windows. Bash treats each \ as an escape, so C:\Users\...\sqz.exe becomes C:Users...sqz.exe → command not found.
Note that generate_hook_configs is shell-agnostic — it does not use ShellHook::detect(). So the bug is independent of which interactive shell is detected; detecting PowerShell vs bash does not change this hook. (ShellHook::detect() only affects which shell RC file the interactive integration is installed into.)
Steps to reproduce
- On Windows with Git Bash present, install sqz and run
sqz init --global.
- Open Claude Code in any project and run any Bash command.
- Observe the
PreToolUse:Bash hook error … command not found on every Bash invocation.
Inspecting ~/.claude/settings.json shows the three hook commands (PreCompact, PreToolUse/Bash, SessionStart) all using C:\\Users\\...\\sqz.exe.
Expected
The hook command should run under Git Bash. Either of these works (both verified on the affected machine):
- Normalise separators to
/ when writing the hook on Windows, e.g. C:/Users/.../sqz.exe hook claude (Git Bash and cmd/PowerShell all accept forward-slash absolute exe paths), or
- Emit a bare
sqz (sqz hook claude) since the install puts sqz on PATH — and notably the rewrite that the hook itself produces already uses bare sqz (... 2>&1 | sqz compress --cmd git), which works fine. Only the hook invocation path is broken.
Suggested fix
Most robust: write the hook in exec form. Claude Code command hooks accept an optional args array; when present, command is resolved as an executable and spawned directly, with no shell involved (hooks reference). That sidesteps backslash escaping entirely, on every shell and platform:
{
"type": "command",
"command": "C:\\Users\\...\\sqz.exe",
"args": ["hook", "claude"]
}
This is strictly better than the shell-form path because it works regardless of which shell Claude Code uses to run hooks.
Shell-form alternatives (if args isn't adopted):
- Normalise separators to
/ in the current_exe() path (e.g. C:/Users/.../sqz.exe), which Git Bash, cmd, and PowerShell all accept; or
- Set
"shell": "powershell" on the hook so the backslash path is run by PowerShell rather than Git Bash; or
- Emit a bare
sqz (it's on PATH, and the rewrite the hook produces already uses bare sqz).
Why the shell matters here: per the Claude Code overview, on native Windows the Bash tool (and command hooks) run through Git Bash when Git for Windows is installed (the recommended setup), and PowerShell otherwise. So this bug only manifests on Git-Bash-backed machines — a PowerShell-fallback Windows user would see the same C:\Users\...\sqz.exe path run fine, which likely explains why it has gone largely unreported. Exec form avoids the shell question altogether.
Workaround for affected users
Edit ~/.claude/settings.json and change \\ to / in the three sqz hook command paths. (Re-running sqz init re-introduces the backslashes.)
Summary
On Windows,
sqz initwrites the Claude CodePreToolUsehook intosettings.jsonusing a backslash Windows path to the executable (C:\Users\...\sqz.exe). Claude Code executes hook commands through Git Bash (/usr/bin/bash), where\is the escape character, so the path collapses and the hook fails on every Bash tool call:The failure is non-blocking, so commands still run — but the hook never executes, so no output compression happens, and the user sees the error on every command.
Relationship to #2 (this is a distinct, follow-on bug)
#2 ("
sqz initdoesn't escape\in .claude/settings.local.json") was about the generated JSON being invalid — unescaped backslashes broke JSON parsing so Claude couldn't read the file. That has been fixed: the file now contains correctly-escapedC:\\Users\\...\\sqz.exe, which is valid JSON.This issue is one layer down. The JSON now parses fine, but the path value it parses to (
C:\Users\...\sqz.exe) is a Windows backslash path, and Claude Code executes hook commands through Git Bash — where backslashes are shell escapes. So the very escaping fix from #2 is what surfaces this: valid JSON, but a command string bash can't run. #2 fixed the JSON layer; this is the shell-execution layer.Environment
MINGW64_NT, bash 5.3.x) — confirmed by the/usr/bin/bash: line 1:prefix in the errorsqz init --globalRoot cause
In
sqz/src/main.rs, the executable path is taken straight fromcurrent_exe()with no separator normalisation:That
sqz_pathis then passed tosqz_engine::generate_hook_configs(&sqz_path), which emits the Claude Code hookcommandverbatim, e.g.:Claude Code runs that command via Git Bash on Windows. Bash treats each
\as an escape, soC:\Users\...\sqz.exebecomesC:Users...sqz.exe→command not found.Note that
generate_hook_configsis shell-agnostic — it does not useShellHook::detect(). So the bug is independent of which interactive shell is detected; detecting PowerShell vs bash does not change this hook. (ShellHook::detect()only affects which shell RC file the interactive integration is installed into.)Steps to reproduce
sqz init --global.PreToolUse:Bash hook error … command not foundon every Bash invocation.Inspecting
~/.claude/settings.jsonshows the three hook commands (PreCompact,PreToolUse/Bash,SessionStart) all usingC:\\Users\\...\\sqz.exe.Expected
The hook command should run under Git Bash. Either of these works (both verified on the affected machine):
/when writing the hook on Windows, e.g.C:/Users/.../sqz.exe hook claude(Git Bash and cmd/PowerShell all accept forward-slash absolute exe paths), orsqz(sqz hook claude) since the install putssqzon PATH — and notably the rewrite that the hook itself produces already uses baresqz(... 2>&1 | sqz compress --cmd git), which works fine. Only the hook invocation path is broken.Suggested fix
Most robust: write the hook in exec form. Claude Code command hooks accept an optional
argsarray; when present,commandis resolved as an executable and spawned directly, with no shell involved (hooks reference). That sidesteps backslash escaping entirely, on every shell and platform:{ "type": "command", "command": "C:\\Users\\...\\sqz.exe", "args": ["hook", "claude"] }This is strictly better than the shell-form path because it works regardless of which shell Claude Code uses to run hooks.
Shell-form alternatives (if
argsisn't adopted):/in thecurrent_exe()path (e.g.C:/Users/.../sqz.exe), which Git Bash,cmd, and PowerShell all accept; or"shell": "powershell"on the hook so the backslash path is run by PowerShell rather than Git Bash; orsqz(it's on PATH, and the rewrite the hook produces already uses baresqz).Why the shell matters here: per the Claude Code overview, on native Windows the Bash tool (and command hooks) run through Git Bash when Git for Windows is installed (the recommended setup), and PowerShell otherwise. So this bug only manifests on Git-Bash-backed machines — a PowerShell-fallback Windows user would see the same
C:\Users\...\sqz.exepath run fine, which likely explains why it has gone largely unreported. Exec form avoids the shell question altogether.Workaround for affected users
Edit
~/.claude/settings.jsonand change\\to/in the three sqz hookcommandpaths. (Re-runningsqz initre-introduces the backslashes.)