# Your Agent Doesn't Run Your Hooks

> Guardrails in one AI tool's settings bind only that tool. Rules that must hold belong in git: pre-commit hooks, CI and tests every writer has to pass.

Canonical: https://mikereams.com/writing/your-agent-doesnt-run-your-hooks

Published: August 14, 2026  
Author: Mike Reams (https://mikereams.com/about)  
Topics: [AI in the Architecture Practice](https://mikereams.com/writing/topics/ai-in-the-architecture-practice), [Architecture](https://mikereams.com/writing/topics/architecture), [Artificial Intelligence](https://mikereams.com/writing/topics/artificial-intelligence)  
Tags: AI, AI Agents, Git  
Project: [AI-Maintained Architecture Second-Brain Repository](https://mikereams.com/work/ai-maintained-architecture-knowledge-base)

![Diagram: four writers — an AI tool, a second AI surface, another vendor's agent and a person — bypass tool-specific hooks, but all must pass through git, where pre-commit hooks, CI and tests enforce the rules.](https://mikereams.com/writing/your-agent-doesnt-run-your-hooks.png)

I wrote guardrails for my AI-maintained knowledge base the way the tool's documentation suggests: hooks in the project settings. Check the session state when work starts. Refuse edits to the raw evidence folder. Block non-ASCII characters in import files. Thorough, reasonable, well-tested in the command-line tool.

Then I ran the same project through a different surface built on the same agent framework, and none of it fired. I proved it with a probe hook that did nothing but write a line to a log file. Two full restarts later, the log was still zero bytes. The guardrails were not broken. They were just never invited.

## Instructions are not enforcement

Every AI tool has somewhere to put rules: an instructions file, a settings file, a hooks block. Those rules bind exactly one runtime — the one that reads them. A different tool, a different surface of the same product, a second agent from another vendor, or a human at a keyboard will walk straight past them.

> A guardrail that only one tool reads isn't a guardrail. It's a strongly worded suggestion with a config file.

## Enforce where every writer has to pass

The question is not "where can I put this rule?" It is "what does every change have to go through?" For a repository, the answer is git. Every writer — any agent, any surface, any person — eventually commits.

- **Pre-commit hooks.** A tracked hooks folder, activated once per clone, rejects a commit that touches protected paths or breaks a file rule. It fires no matter which tool wrote the file.
- **CI checks.** The same checks run again on the server, because hooks can be skipped locally and a pipeline cannot. If it matters, it fails the build.
- **Tests as invariants.** Rules about content — every published post links only to published posts, every project lists only real slugs — become tests. [Green tests can still lie](https://mikereams.com/writing/green-tests-broken-system), so test the outcome, not the mechanism.

## The trade-off, honestly

Git-layer enforcement catches problems at commit time, not edit time. The agent can still write the bad file; it just cannot land it. That is later than an in-tool hook, and it means some wasted work. It is also the only layer I have found that holds when the tool changes underneath you, which, in this field, is roughly every Tuesday.

Keep the in-tool instructions too. They are good at steering. Just do not mistake steering for a fence. The same lesson applies to deployments: [pushed is not deployed](https://mikereams.com/writing/pushed-is-not-deployed), and written down is not enforced.

## Copy this: a tool-agnostic pre-commit hook

```
#!/bin/sh
# .githooks/pre-commit - activate once per clone:
#   git config core.hooksPath .githooks
# Fires for every writer: any agent, any surface, any human.
staged=$(git diff --cached --name-only)

# 1. Evidence is append-only: no edits to raw/ without an explicit flag
if echo "$staged" | grep -q '^raw/' && [ -z "$ALLOW_RAW" ]; then
  echo "pre-commit: raw/ is read-only evidence (set ALLOW_RAW=1 to override)"; exit 1
fi

# 2. Import files must be ASCII (spreadsheets mangle smart quotes)
for f in $(echo "$staged" | grep '^imports/.*\.csv$'); do
  if LC_ALL=C grep -q '[^ -~]' "$f"; then
    echo "pre-commit: non-ASCII characters in $f"; exit 1
  fi
done

# 3. The same checks run in CI - hooks can be skipped, pipelines cannot
exit 0
```
