ADR-2012: Lefthook and the pre-commit framework on Windows hosts¶
- Status: Accepted
- Date: 2026-10-06
- Deciders: Lusoris
- Tags: ci, tooling, workspace, agents, git-hooks, windows
Context¶
Lefthook had never been installed on the Windows 11 workstation running agent checkouts, leaving local commits without pre-commit or commit-msg checks. When lefthook was first installed on Windows, several defects prevented the ADR-1249 hook stack from passing in Git Bash:
- Lefthook unescaped execution: On Windows, Lefthook v2.1.14 (
internal/run/controller/exec/exec_windows.go) executes commands via"<sh>" -c "<run>"without escaping the script argument. The first double quote terminates the-cargument, resulting in syntax errors on multi-line blocks (unexpected end of file from 'if' command) and silent loss of arguments. - Drive letter colon in
PATH: Lefthook passes Git Bash a working directory formatted asC:/.... When prepending.venv/ScriptstoPATH, the colon inC:splits the PATH entry, breaking tool discovery (e.g.reuse.exe). - Hook installer collision:
scripts/githooks/install.pytreats any unknown hook file as a custom hook and refuses to run. Lefthook'spre-commitandpre-pushshims causedmake install-hooksto abort, preventing installation of thecommit-msg(Conventional Commits) andpre-rebase(worktree drift guard) dispatchers (ADR-1241). - Agent hook file churn:
lefthook uninstallre-marshals.claude/settings.jsonand.codex/hooks.jsonwith Go'sjson.MarshalIndent(sorted keys, 2-space indentation), dirtying the checkout even when no hooks are removed. - Windows-specific toolchain and build requirements:
reuse6.2.0 cannot usepython-magicon Windows and fails unless a pure-Python encoding detector (charset-normalizer) is installed.cmd/vmafx-node/bpf/referencedcilium/ebpf/link.Tracepoint, which is unavailable on Windows, causinggo vet ./...(run by lefthook'sgovethook) to fail.- Text fixtures and scripts wrote CRLF line endings on Windows, failing byte-exact generator checks.
- Path separators in
check-container-image-references.pyfailed against/-keyed exceptions on Windows.
Decision¶
- Quote-free lefthook delegations:
lefthook.ymlkeeps everyrun:command on a single line with zero double quotes and zero block scalars (|,>). Complex stage execution is moved to standalone shell scripts (scripts/git-hooks/framework-hooks.sh), which execute underbash. - Path normalization:
scripts/git-hooks/framework-hooks.shusescygpath -uto convert WindowsC:/...paths before prepending them toPATH. It supports.venv/Scripts/pre-commit.exeon Windows and.venv/bin/pre-commiton POSIX systems. - Installer coexistence:
scripts/githooks/install.pyrecognises Lefthook shims (call_lefthook run) as owned by Lefthook and leaves them in place. The supported installation order islefthook installfollowed bymake install-hooks. - Normalized agent hook settings:
.claude/settings.jsonand.codex/hooks.jsonare committed formatted with sorted keys and 2-space indentation matching Go'sjson.MarshalIndent. - Cross-platform build hygiene:
requirements/locks/pre-commit.txtinstallsreuse[charset-normalizer]==6.2.0.cmd/vmafx-node/bpf/files referencingcilium/ebpftracepoints carry//go:build linux.- Python fixture generators and doc scripts enforce
newline="\n". check-container-image-references.pyusespath.as_posix()for comparison.- Added
windows-hooksCI job to.github/workflows/standards-gate.ymlrunning onwindows-2025to continuously verify lefthook pre-commit in a scratch clone.
Alternatives considered¶
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
| Single-line quote-free runner script (chosen) | Robust against Lefthook escaping bugs; easily tested; cross-platform | Adds scripts/git-hooks/framework-hooks.sh | Avoids fragile escaping workarounds in YAML |
Escaping quotes inside lefthook.yml | No helper script | Unescaped execution in Lefthook v2.1.14 cannot be safely escaped without engine changes | Fragile and broken in upstream Lefthook |
Move all hooks into lefthook.yml | One hook manager | Reverses ADR-1249 and loses ADR-1241 worktree drift guard | Unnecessary policy divergence |
| Run pre-commit directly without Lefthook on Windows | Simple | Bypasses HISS governance checks (praetorctl audit, hiss coverage) | Violates repository governance invariants |
Consequences¶
- Positive: Windows developers and agent runners can run the full pre-commit and pre-push governance stack locally.
- Positive: Continuous CI coverage for Windows lefthook execution on
windows-2025. - Negative: Installation order is strictly
lefthook installfirst, thenmake install-hooks. - Follow-up:
scripts/githooks/tests/test_install.pyenforces the quote-free and agent-settings invariants viaLefthookBridgeTests.
References¶
- ADR-1249 — Praetor governance adoption and Lefthook hook ownership.
- ADR-1241 — Worktree hook dispatchers.
- Pre-commit hooks guide — Setup and usage documentation.
- Upstream Lefthook issue:
exec_windows.gounescapedsh -cexecution.