Seed git's clone template with the pre-commit hooks #986

Merged
coilyco-ops merged 1 commit from aos/claude/ee98-hooktemplate into main 2026-08-29 06:53:18 +00:00
Owner

Closes the fresh-clone half of coilyco-flight-deck/agentic-os#1382. The resident-checkout half landed as agentic-os#1385.

The gap

pre-commit writes a config into every catalog repository and nothing installed the git hooks that run it, so the gate bound only where somebody had run an install by hand. For a non-resident repository like housecast, a fresh clone is every session rather than an edge case, so the step was due every time and its result thrown away every time. Two agents hit it in one evening and one pushed 8 violations to main.

What this adds

A git-hook-template role that seeds ~/.config/git/template/hooks with pre-commit init-templatedir and points global init.templateDir at it. Every later clone is gated from its first commit, with no install step.

pre-push is in the hook set deliberately. init-templatedir with no --hook-type seeds pre-commit alone. Taking that default would have reproduced the exact gap being closed: commits gated, pushes silently not, and the repair would have looked landed. The science seat measured that default and flagged it before I got there.

Verified, both halves

Only the second proves pre-push landed, which is why it is tested separately.

git clone --template=T <repo>   ->  .git/hooks: commit-msg pre-commit pre-push prepare-commit-msg
violating commit                ->  refused (documentation-layout), HEAD unchanged
push to main, lane flipped      ->  refused: [pr-guard] refusing to push directly to 'main'
second converge                 ->  ok=7 changed=0

The lane flip was synthetic and necessary: every repository that enables pr-guard today is on merge-remote-main, the one lane the guard now stands down on, so nothing in the estate would have exercised the refusing path.

Two properties that shaped the implementation

  • the generated hook embeds an absolute interpreter path for the host that produced it, so the template is generated per host rather than committed and copied. That is why this is a role and not a checked-in template.
  • the hook carries --skip-on-missing-config, so a clone with no .pre-commit-config.yaml no-ops rather than erroring. The setting applies to every clone on the host, and this is what bounds it to catalog repositories in effect.

Both halves are needed

init.templateDir applies at clone and init only, so it never reaches a checkout that already exists. Those are covered by agentic-os's rollout script, which now installs pre-push too, and which I ran across the fleet: 18 of 20 resident checkouts carry it, the two exceptions being the deliberate .agentic-os-ignore opt-out and agentic-os itself, which the script skips as the source repo.

Not established

Whether this should also run on Windows hosts. I did not guard the role by OS, so it will attempt there, and the which pre-commit check degrades it to a no-op if the binary is absent. Whether the template path and hook shebang behave under native Windows git is untested, and group_vars/windows.yml is staged rather than converged today anyway.

Closes the fresh-clone half of `coilyco-flight-deck/agentic-os#1382`. The resident-checkout half landed as `agentic-os#1385`. ## The gap `pre-commit` writes a config into every catalog repository and **nothing installed the git hooks that run it**, so the gate bound only where somebody had run an install by hand. For a non-resident repository like `housecast`, a fresh clone is every session rather than an edge case, so the step was due every time and its result thrown away every time. Two agents hit it in one evening and one pushed 8 violations to `main`. ## What this adds A `git-hook-template` role that seeds `~/.config/git/template/hooks` with `pre-commit init-templatedir` and points global `init.templateDir` at it. Every later clone is gated from its first commit, with no install step. **`pre-push` is in the hook set deliberately.** `init-templatedir` with no `--hook-type` seeds `pre-commit` alone. Taking that default would have reproduced the exact gap being closed: commits gated, pushes silently not, and the repair would have looked landed. The science seat measured that default and flagged it before I got there. ## Verified, both halves Only the second proves `pre-push` landed, which is why it is tested separately. ``` git clone --template=T <repo> -> .git/hooks: commit-msg pre-commit pre-push prepare-commit-msg violating commit -> refused (documentation-layout), HEAD unchanged push to main, lane flipped -> refused: [pr-guard] refusing to push directly to 'main' second converge -> ok=7 changed=0 ``` The lane flip was synthetic and necessary: **every repository that enables `pr-guard` today is on `merge-remote-main`**, the one lane the guard now stands down on, so nothing in the estate would have exercised the refusing path. ## Two properties that shaped the implementation * the generated hook embeds an **absolute interpreter path** for the host that produced it, so the template is generated per host rather than committed and copied. That is why this is a role and not a checked-in template. * the hook carries `--skip-on-missing-config`, so a clone with no `.pre-commit-config.yaml` no-ops rather than erroring. The setting applies to every clone on the host, and this is what bounds it to catalog repositories in effect. ## Both halves are needed `init.templateDir` applies at clone and init only, so it never reaches a checkout that already exists. Those are covered by agentic-os's rollout script, which now installs `pre-push` too, and which I ran across the fleet: 18 of 20 resident checkouts carry it, the two exceptions being the deliberate `.agentic-os-ignore` opt-out and agentic-os itself, which the script skips as the source repo. ## Not established Whether this should also run on Windows hosts. I did not guard the role by OS, so it will attempt there, and the `which pre-commit` check degrades it to a no-op if the binary is absent. Whether the template path and hook shebang behave under native Windows git is untested, and `group_vars/windows.yml` is staged rather than converged today anyway.
feat(ansible): seed git's clone template with the pre-commit hooks
All checks were successful
TruffleHog / Scan for secrets (pull_request) Successful in 5s
CI / lint (pull_request) Successful in 45s
9aa4c18256
`pre-commit` writes a config into every catalog repository and nothing installed
the git hooks that run it, so the commit gate bound only where somebody had run
an install by hand. For a non-resident repository like housecast a fresh clone is
every session rather than an edge case, so the step was due every time and thrown
away every time. Two agents hit it in one evening and one pushed 8 violations to
main (agentic-os#1382).

The failure class is the one this estate keeps meeting: a check that does not run
reads exactly like a check that passed.

The role seeds ~/.config/git/template/hooks with `pre-commit init-templatedir`
and points global `init.templateDir` at it, so every later clone is gated from
its first commit with no install step.

`pre-push` is in the hook set deliberately. pr-guard runs at that stage, and
`init-templatedir` with no `--hook-type` seeds pre-commit alone, which would have
reproduced the exact gap being closed: commits gated, pushes silently not.

Verified, both halves, because only the second proves pre-push landed. A fresh
clone through the template refuses a violating commit, and on a clone whose lane
was flipped so the guard should bite, `git push` to main is refused by pr-guard.
The role is idempotent on a second converge.

Generation is per host rather than a committed template, because the generated
hook embeds an absolute interpreter path for the host that produced it. The hook
also carries `--skip-on-missing-config`, so a clone with no `.pre-commit-config.yaml`
no-ops instead of erroring, which bounds this to catalog repositories in effect.

`init.templateDir` reaches clone and init only, so existing checkouts are covered
instead by agentic-os's rollout script, which now installs pre-push too. Both
halves are needed and neither is sufficient.

Refs coilyco-flight-deck/agentic-os#1382

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Agent-Role: platform
coilyco-ops deleted branch aos/claude/ee98-hooktemplate 2026-08-29 06:53:18 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/infrastructure!986
No description provided.