mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-10 14:27:00 -04:00
fix(init): normalize Windows hook paths to forward slashes (#788)
## Description
On Windows, `_command_string()` preserves backslash paths from
`shutil.which()` (e.g. `C:\Users\...\headroom.exe`). Claude Code
executes hooks via Git Bash, which interprets backslashes as escape
characters, corrupting the path and failing with "command not found".
This PR normalizes backslash separators to forward slashes before
passing parts to `subprocess.list2cmdline()`. Forward slashes work in
bash, PowerShell, and cmd.exe on Windows.
Fixes #724
## Type of Change
- [x] Bug fix (non-breaking change that fixes an issue)
## Changes Made
- `headroom/cli/init.py`: Normalize backslash path separators to forward
slashes in `_command_string()` on Windows, before calling
`subprocess.list2cmdline()`
- `tests/test_cli/test_init_cli.py`: Add
`test_command_string_normalizes_backslashes_on_windows` verifying no
backslashes remain in the output and the forward-slash path is preserved
## Real behavior proof
**Setup:** Windows 11 (build 26200), Python 3.10.18, headroom repo at
commit 9579567
**Before fix** — `_command_string()` output with a typical Windows path:
```
C:\Users\sheng\.local\bin\headroom.exe init hook ensure --profile default
```
Git Bash interprets `\U`, `\s`, `\.`, `\b`, `\h` as escape sequences →
command not found.
**After fix** — same input, normalized output:
```
C:/Users/sheng/.local/bin/headroom.exe init hook ensure --profile default
```
Forward slashes pass through Git Bash, PowerShell, and cmd.exe without
corruption.
**Edge case — path with spaces** (quoting preserved):
```
"C:/Program Files/headroom/headroom.exe" init hook ensure
```
**What I did not test:** Live `headroom init claude` end-to-end
(headroom native extension build fails on this machine due to Rust
download timeout). The fix is exercised by the unit test which uses the
real `subprocess.list2cmdline` on Windows.
## Testing
- [x] Unit tests pass (`pytest`) — 50/50 passed in `test_init_cli.py`
- [x] Linting passes (`ruff check .`)
- [x] Formatting passes (`ruff format --check .`)
- [x] New tests added for new functionality
## Test Output
```
$ python -m pytest tests/test_cli/test_init_cli.py -v
50 passed, 3 warnings in 4.02s
```
## Checklist
- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have added tests that prove my fix is effective
- [x] New and existing unit tests pass locally with my changes
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
84ac332d14
commit
6ea6e31f09
2 changed files with 27 additions and 0 deletions
|
|
@ -53,6 +53,9 @@ _STARTUP_READY_TIMEOUT_SECONDS = 15
|
|||
|
||||
def _command_string(parts: list[str]) -> str:
|
||||
if os.name == "nt":
|
||||
# Normalize backslash paths to forward slashes so hook commands
|
||||
# work when Claude Code executes them via Git Bash (#724).
|
||||
parts = [p.replace("\\", "/") for p in parts]
|
||||
return subprocess.list2cmdline(parts)
|
||||
return shlex.join(parts)
|
||||
|
||||
|
|
|
|||
|
|
@ -336,6 +336,30 @@ def test_command_string_and_matcher_on_windows(monkeypatch) -> None:
|
|||
assert init_cli._powershell_matcher() == "Bash|PowerShell"
|
||||
|
||||
|
||||
def test_command_string_normalizes_backslashes_on_windows(monkeypatch) -> None:
|
||||
"""Backslash paths must become forward slashes so Git Bash hooks work (#724)."""
|
||||
init_cli, _ = _load_init_module(monkeypatch)
|
||||
monkeypatch.setattr(init_cli, "os", SimpleNamespace(name="nt"))
|
||||
|
||||
result = init_cli._command_string(
|
||||
["C:\\Users\\user\\.local\\bin\\headroom.exe", "init", "hook", "ensure"]
|
||||
)
|
||||
assert "\\" not in result
|
||||
assert "C:/Users/user/.local/bin/headroom.exe" in result
|
||||
|
||||
|
||||
def test_command_string_quotes_spaces_after_normalization(monkeypatch) -> None:
|
||||
"""Paths with spaces must stay properly quoted after backslash normalization (#724)."""
|
||||
init_cli, _ = _load_init_module(monkeypatch)
|
||||
monkeypatch.setattr(init_cli, "os", SimpleNamespace(name="nt"))
|
||||
|
||||
result = init_cli._command_string(
|
||||
["C:\\Program Files\\headroom\\headroom.exe", "init", "hook", "ensure"]
|
||||
)
|
||||
assert "\\" not in result
|
||||
assert '"C:/Program Files/headroom/headroom.exe"' in result
|
||||
|
||||
|
||||
def test_json_file_handles_missing_empty_and_non_mapping(monkeypatch, tmp_path: Path) -> None:
|
||||
init_cli, _ = _load_init_module(monkeypatch)
|
||||
missing = tmp_path / "missing.json"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue