A fresh clone arrives with no hooks, so agent commits in temporary clones are unvalidated #1163

Open
opened 2026-08-20 09:29:52 +00:00 by coilyco-ops · 0 comments
Owner

Cloning a repo and committing in it is a normal agent move, and today that commit runs through no pre-commit hook at all. Hook arming is a separate fleet pass over on-disk checkouts, so anything cloned after that pass is silently unguarded.

How this surfaced

The fleet CODEOWNERS removal (coilyco-bridge/agentic-os-kai#889) had to reach 16 repos with no local checkout. Each was cloned to a temporary directory, edited, committed, and pushed. Not one of those commits ran a hook, because git clone does not install any. For a deletion of a file nothing references that was acceptable, and it was reported as such, but the same path would have carried a secret, a malformed YAML, or a docs-layout violation straight to main with no gate.

The agent doctrine actively pushes work down this path. "Unlisted repository clones stay temporary" tells an agent to clone unlisted repos into a temp dir and work there, which is precisely the case that arrives unarmed.

The arming is already inconsistent

scripts/apply-agentic-os-hooks.py documents step 4 as running:

pre-commit install --hook-type pre-commit --hook-type commit-msg --hook-type prepare-commit-msg

and scripts/apply-agentic-os-hooks.py:463-465 matches that. pre-push is absent from it, yet the catalog ships pre-push hooks: pr-guard is stages: [pre-push], and check-skills, check-composed-skills, dead-cross-links, and trufflehog are [pre-commit, pre-push].

On disk right now the two checkouts disagree:

agentic-os/.git/hooks:     pre-commit  pre-push  pre-push.legacy
infrastructure/.git/hooks: commit-msg  pre-commit  prepare-commit-msg  pre-push  pre-push.legacy

So pre-push got armed by some path other than the documented installer, and commit-msg is armed in one repo and not the other. Nothing reconciles these, because arming lives outside the thing that creates a checkout.

The ask

Make a clone arrive armed, so hook coverage is a property of having a checkout rather than of a sweep having visited it.

Suggested shape

A wrapper around git clone is the obvious framing, and it is worth building for the ergonomics, but it only covers clones that go through the wrapper. An agent that calls bare git clone, or a tool that shells out to it, keeps the current behaviour. The mechanism that covers every clone regardless of caller is git's own template directory, which pre-commit supports directly:

pre-commit init-templatedir --allow-missing-config ~/.git-template
git config --global init.templateDir ~/.git-template

Every subsequent git clone and git init copies the armed hooks in. Neither is set on this host today (init.templateDir and core.hooksPath are both unset, and ~/.git-template does not exist), so this is net-new rather than drift.

Points to settle:

  • --allow-missing-config is required. Without it, cloning any repo that ships no .pre-commit-config.yaml, including vendored and third-party trees, breaks on first commit. With it, such a repo stays silent.
  • Decide the hook-type set once and make it the same everywhere: pre-commit, commit-msg, prepare-commit-msg, and pre-push. Whatever armed pre-push outside the installer should be folded in or retired, and pre-push.legacy explained.
  • init.templateDir arms hooks but does not build environments, so the first commit in a fresh clone pays the pre-commit install-hooks cost. Acceptable, worth knowing, and a wrapper could prefetch it.
  • A shallow or throwaway clone wants the hooks too. That is the case this issue exists for, so the wrapper must not skip arming for --depth 1.
  • This does not replace apply-agentic-os-hooks.py. That tool authors each repo's .pre-commit-config.yaml, which is repo content. This only arms the checkout, which is local git state. Keeping the two separate keeps the authoring-vs-rollout split intact.

Authoring vs rollout

The wrapper and the hook-type decision are authored here, next to the catalog they arm. Converging init.templateDir and the template directory onto every host is fleet mutation and belongs in an infrastructure ansible role, not in ward setup or a brew post-install.

Cloning a repo and committing in it is a normal agent move, and today that commit runs through no pre-commit hook at all. Hook arming is a separate fleet pass over on-disk checkouts, so anything cloned after that pass is silently unguarded. ## How this surfaced The fleet CODEOWNERS removal (coilyco-bridge/agentic-os-kai#889) had to reach 16 repos with no local checkout. Each was cloned to a temporary directory, edited, committed, and pushed. Not one of those commits ran a hook, because `git clone` does not install any. For a deletion of a file nothing references that was acceptable, and it was reported as such, but the same path would have carried a secret, a malformed YAML, or a docs-layout violation straight to `main` with no gate. The agent doctrine actively pushes work down this path. "Unlisted repository clones stay temporary" tells an agent to clone unlisted repos into a temp dir and work there, which is precisely the case that arrives unarmed. ## The arming is already inconsistent `scripts/apply-agentic-os-hooks.py` documents step 4 as running: ``` pre-commit install --hook-type pre-commit --hook-type commit-msg --hook-type prepare-commit-msg ``` and `scripts/apply-agentic-os-hooks.py:463-465` matches that. `pre-push` is absent from it, yet the catalog ships pre-push hooks: `pr-guard` is `stages: [pre-push]`, and `check-skills`, `check-composed-skills`, `dead-cross-links`, and `trufflehog` are `[pre-commit, pre-push]`. On disk right now the two checkouts disagree: ``` agentic-os/.git/hooks: pre-commit pre-push pre-push.legacy infrastructure/.git/hooks: commit-msg pre-commit prepare-commit-msg pre-push pre-push.legacy ``` So `pre-push` got armed by some path other than the documented installer, and `commit-msg` is armed in one repo and not the other. Nothing reconciles these, because arming lives outside the thing that creates a checkout. ## The ask Make a clone arrive armed, so hook coverage is a property of having a checkout rather than of a sweep having visited it. ## Suggested shape A wrapper around `git clone` is the obvious framing, and it is worth building for the ergonomics, but it only covers clones that go through the wrapper. An agent that calls bare `git clone`, or a tool that shells out to it, keeps the current behaviour. The mechanism that covers every clone regardless of caller is git's own template directory, which pre-commit supports directly: ```sh pre-commit init-templatedir --allow-missing-config ~/.git-template git config --global init.templateDir ~/.git-template ``` Every subsequent `git clone` and `git init` copies the armed hooks in. Neither is set on this host today (`init.templateDir` and `core.hooksPath` are both unset, and `~/.git-template` does not exist), so this is net-new rather than drift. Points to settle: - `--allow-missing-config` is required. Without it, cloning any repo that ships no `.pre-commit-config.yaml`, including vendored and third-party trees, breaks on first commit. With it, such a repo stays silent. - Decide the hook-type set once and make it the same everywhere: `pre-commit`, `commit-msg`, `prepare-commit-msg`, and `pre-push`. Whatever armed `pre-push` outside the installer should be folded in or retired, and `pre-push.legacy` explained. - `init.templateDir` arms hooks but does not build environments, so the first commit in a fresh clone pays the `pre-commit install-hooks` cost. Acceptable, worth knowing, and a wrapper could prefetch it. - A shallow or throwaway clone wants the hooks too. That is the case this issue exists for, so the wrapper must not skip arming for `--depth 1`. - This does not replace `apply-agentic-os-hooks.py`. That tool authors each repo's `.pre-commit-config.yaml`, which is repo content. This only arms the checkout, which is local git state. Keeping the two separate keeps the authoring-vs-rollout split intact. ## Authoring vs rollout The wrapper and the hook-type decision are authored here, next to the catalog they arm. Converging `init.templateDir` and the template directory onto every host is fleet mutation and belongs in an infrastructure ansible role, not in `ward setup` or a brew post-install.
Sign in to join this conversation.
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/agentic-os#1163
No description provided.