Skip to content

PreToolUse hooks fail on Windows with "command not found", claude install writes backslash exe paths that Git Bash strips #1987

Description

@varuntej07

TL;DR: On Windows, graphify claude install writes the PreToolUse hook commands using the executable's native backslash path (C:\Users...\graphify.EXE). Claude Code runs hooks through Git Bash, which treats ** as an escape character and strips them, so the path collapses to C:Usersvarn...graphify.EXE and every invocation fails with command not found. All graph guards, search and read, are silently dead on Windows.

How I found this:

While digging into the Grep matcher issue (#1986 ), I noticed my Claude Code terminal was quietly printing hook errors on almost every tool call. They weren't the guard choosing not to fire, they were the guard's command failing to execute at all. Once I read the actual path in the error, the backslashes were simply gone.

Summary

_claude_pretooluse_hooks() builds each hook command as f"{exe} hook-guard ...", where exe is the resolved executable path. On Windows that path uses backslashes, and it lands in .claude/settings.json as C:\\Users\\...\\graphify.EXE. Claude Code on Windows executes PreToolUse hook commands through Git Bash (/usr/bin/bash, visible in the error). Bash interprets each ** as an escape, so \U \v . \b all collapse to their next character and the path is destroyed before the shell ever looks for the binary. The result is not a miss-fire, it's a hard failure on every guarded tool call.

Evidence (real 0.9.18 session, Windows)

PreToolUse:Bash hook error
Failed with non-blocking status code: /usr/bin/bash: line 1: C:Usersvarun.localbingraphify.EXE: command not found
PreToolUse:Read hook error
Failed with non-blocking status code: /usr/bin/bash: line 1: C:Usersvarun.localbingraphify.EXE: command not found

Note the path in the error: every backslash from C:\Users\varun\.local\bin\graphify.EXE is gone.

The exact spot

graphify/install.py, _claude_pretooluse_hooks():

command: f"{exe} hook-guard search"   # exe is a native Windows path: C:\Users\...\graphify.EXE

exe is stored verbatim, so on Windows the emitted settings.json is:

{ "matcher": "Bash",
  "hooks": [{ "type": "command",
              "command": "C:\\Users\\varun\\.local\\bin\\graphify.EXE hook-guard search" }] }

Both the search hook and the Read|Glob read hook use the same exe, so both die identically.

Repro

graphifyy 0.9.18 (uv tool install), Claude Code CLI on Windows:

  1. graphify claude install (in any dir)
  2. Inspect .claude/settings.json -> hook commands use C:\Users\...\graphify.EXE
  3. In Claude Code, trigger any Bash or Read tool call.
  4. The terminal prints: /usr/bin/bash: line 1: C:Users...graphify.EXE: command not found

Actual: every PreToolUse hook fails to execute on Windows; no graph guard ever runs.
Expected: the hook command runs and the graphify nudge fires.

Why bash, and why forward slashes fix it?

A manually corrected block using forward slashes, C:/Users/varun/.local/bin/graphify.EXE, runs cleanly in the same session. Windows accepts / as a path separator, and bash leaves / untouched, so a forward-slash path is correct on every platform. Backslashes only ever break Windows.

Suggested fix

Normalize exe to a forward-slash path before interpolating it into the hook command, e.g. Path(exe).as_posix() (or str(exe).replace("\", "/")). One change covers both the search and read hooks since they share exe.

Note for reproduction: this is Windows-only. On macOS/Linux the resolved path is already /-native, so graphify claude install there emits a working command and the bug will not reproduce locally. The evidence above is from a real Windows 0.9.18 install.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions