mirror of
https://github.com/safishamsi/graphify.git
synced 2026-09-25 06:55:43 +00:00
fix(install): emit hook exe paths with forward slashes so Git Bash does not strip them
Claude Code runs command-type PreToolUse hooks through Git Bash by default on Windows. The resolved exe was emitted as a raw backslash path and quoted only when it contained a space, so a space-free path like C:\Users\me\graphify.EXE reached settings.json unquoted. Git Bash treats the unquoted backslashes as escapes and strips them, producing 'C:Usersmegraphify.EXE: command not found', so every graph guard silently fails. Normalize the path to forward slashes in _resolve_graphify_exe (a no-op on POSIX), fixing the Claude, Gemini, and Codex hook emitters at once.
This commit is contained in:
+18
-10
@@ -1293,23 +1293,31 @@ def _uninstall_opencode_plugin(project_dir: Path) -> None:
|
||||
config_file.write_text(json.dumps(config, indent=2), encoding="utf-8")
|
||||
print(f" {_OPENCODE_CONFIG_PATH} -> plugin deregistered")
|
||||
def _resolve_graphify_exe() -> str:
|
||||
"""Return the absolute path to the graphify executable.
|
||||
"""Return the absolute path to the graphify executable, with forward slashes.
|
||||
|
||||
Falls back to bare 'graphify' if resolution fails. Using an absolute path
|
||||
ensures the hook works in environments where the venv Scripts/ directory is
|
||||
not on PATH (e.g. VS Code Codex extension on Windows).
|
||||
|
||||
The path is normalized to forward slashes so it survives every shell that
|
||||
runs the hook command. On Windows, Claude Code runs command-type hooks
|
||||
through Git Bash by default, where an unquoted backslash is an escape
|
||||
character: a raw ``C:\\Users\\me\\graphify.EXE`` collapses to
|
||||
``C:Usersmegraphify.EXE: command not found`` and the guard silently fails.
|
||||
Forward slashes are accepted by Git Bash, cmd.exe, and PowerShell alike, and
|
||||
``.replace`` is a no-op on POSIX where paths already use forward slashes.
|
||||
"""
|
||||
import shutil
|
||||
found = shutil.which("graphify")
|
||||
if found:
|
||||
return found
|
||||
# Derive from sys.executable: same Scripts/ (Windows) or bin/ (Unix) dir
|
||||
scripts_dir = Path(sys.executable).parent
|
||||
for name in ("graphify.exe", "graphify"):
|
||||
candidate = scripts_dir / name
|
||||
if candidate.exists():
|
||||
return str(candidate)
|
||||
return "graphify"
|
||||
if not found:
|
||||
# Derive from sys.executable: same Scripts/ (Windows) or bin/ (Unix) dir
|
||||
scripts_dir = Path(sys.executable).parent
|
||||
for name in ("graphify.exe", "graphify"):
|
||||
candidate = scripts_dir / name
|
||||
if candidate.exists():
|
||||
found = str(candidate)
|
||||
break
|
||||
return (found or "graphify").replace("\\", "/")
|
||||
def _install_codex_hook(project_dir: Path) -> None:
|
||||
"""Add graphify PreToolUse hook to .codex/hooks.json."""
|
||||
hooks_path = project_dir / ".codex" / "hooks.json"
|
||||
|
||||
@@ -39,6 +39,18 @@ def test_matcher_targets_bash():
|
||||
assert _search_matcher()["matcher"] == "Bash"
|
||||
|
||||
|
||||
def test_hook_command_has_no_backslashes(monkeypatch):
|
||||
# On Windows the resolved exe is a backslash path; Claude Code runs command
|
||||
# hooks through Git Bash by default, which treats an unquoted backslash as an
|
||||
# escape character and strips it (C:\Users\me\graphify.EXE -> C:Usersme...),
|
||||
# breaking every guard. The emitted command must use forward slashes.
|
||||
from graphify.__main__ import _resolve_graphify_exe
|
||||
monkeypatch.setattr("shutil.which", lambda _name: r"C:\Users\me\graphify.EXE")
|
||||
assert _resolve_graphify_exe() == "C:/Users/me/graphify.EXE"
|
||||
for h in _claude_pretooluse_hooks():
|
||||
assert "\\" not in h["hooks"][0]["command"]
|
||||
|
||||
|
||||
def test_command_has_no_shell_syntax():
|
||||
# #522: no POSIX bash that Windows cmd.exe/PowerShell can't parse.
|
||||
cmd = _search_matcher()["hooks"][0]["command"]
|
||||
|
||||
Reference in New Issue
Block a user