Section 01
Prompts ask; hooks decide
A design harness tells an agent what good looks like. Hooks decide whether the agent is allowed to finish. That distinction matters once you stop treating agent sessions as demos and start treating them as production work. Prompt text is advisory. The model can skip a checklist, invent a color, or declare done while contrast still fails. A hook is a shell command the runtime runs at a known lifecycle point. If the command fails closed, the session does not quietly ship the bad state.
Claude Code and Gemini CLI both expose this control plane. Claude Code configures hooks in `.claude/settings.json` (or user-level settings) and fires events such as `PreToolUse`, `PostToolUse`, and `Stop`. Gemini CLI configures hooks in `.gemini/settings.json` and fires events such as `BeforeTool`, `AfterTool`, and `AfterAgent`. The names differ; the job is the same: run deterministic checks at the moment a write is about to happen, after a write lands, or when the agent claims the turn is finished.
For design teams, the highest-leverage gates are not generic linters alone. They are the checks that catch the failure modes agents invent under time pressure: hardcoded brand colors that bypass tokens, spacing that ignores the scale, purple-gradient “AI default” chrome, missing focus states, and contrast that only looks fine on a designer’s calibrated display. This article shows how to wire those checks as hooks so token drift and accessibility issues block completion instead of becoming a comment on a pull request two days later.
Section 02
Where gates sit in the agent turn
Think of three enforcement moments. Before a tool runs, you can refuse the action: Claude’s `PreToolUse` and Gemini’s `BeforeTool` are the right place to block destructive or out-of-policy commands, and to refuse writes into paths that should never be agent-edited (token source files, locked brand assets). After a successful write, Claude’s `PostToolUse` and Gemini’s `AfterTool` are the right place to format, lint, and run cheap file-scoped audits on the paths just touched. When the agent tries to stop, Claude’s `Stop` hook and Gemini’s `AfterAgent` are the right place for package-level proof: token audit, route accessibility scan, and any custom design verify script you already run in CI.
Exit codes are part of the contract. In Claude Code, exit code 2 is the blocking signal for most events — including preventing a tool call on `PreToolUse` and preventing the model from stopping on `Stop` so it can act on the failure feedback. Exit 0 with structured JSON is the alternative path when you need a precise `decision: "block"` and a reason. Treat exit 1 as a non-blocking hook error unless you have verified your tool’s semantics. Gemini CLI waits for matching hooks to complete before continuing the loop; configure deny decisions through the documented hook output schema so a failed design check cannot be ignored by a confident summary message.
Scope matchers tightly. Running a full accessibility sweep after every keystroke-level edit burns time and tokens. Match Write/Edit (Claude) or `write_file|replace` (Gemini) for file-local checks, and reserve the full `npm run verify` / axe sweep for the stop/after-agent gate. The diagram below is the mental model you should keep on the wall of the studio.
Design decision
Brief + harness loaded — DESIGN.md, AGENTS.md, skills (not hooks yet)
Design decision
PreToolUse / BeforeTool — block out-of-policy paths and destructive Bash
Design decision
Write/Edit lands — agent changes components or styles
Design decision
PostToolUse / AfterTool — format + file-scoped token/a11y lint
Design decision
Stop / AfterAgent — run package verify; fail closed on drift or axe P0
Design decision
Human review — hierarchy, taste, and copy still require eyes
Three enforcement moments in one agent turn: refuse the write, audit the file, then block stop until package-level design QA passes.
Section 03
Gate 1: token drift that cannot ship
Token drift is the quiet failure of agentic UI. The page compiles. The colors are almost right. Somewhere in a component, the agent wrote `text-[#4F46E5]` or `bg-purple-500` because that pattern is overrepresented in training data. Your DESIGN.md said semantic tokens only. CI might catch it later — or it might not, if the only check is TypeScript. A hook makes the rule executable at the moment of authorship.
The practical pattern is a small audit script the repo already owns (this school uses an agentic design audit in verify). The hook’s job is not to reimplement the audit; it is to refuse completion when the audit fails. On Claude Code, a `Stop` hook that runs the audit and exits 2 on failure keeps the turn open and feeds stderr back to the model. On Gemini CLI, an `AfterAgent` (or equivalent post-turn) hook that denies continuation with the audit output does the same job. Pair that with a `PostToolUse` / `AfterTool` matcher on style and component paths so the agent gets a fast signal after each write, not only at the end.
Keep the drift rules concrete. Ban raw hex and arbitrary Tailwind palette colors in product UI files. Require semantic classes (`bg-primary`, `text-muted-foreground`, `border`). If you transform DTCG tokens through Style Dictionary into CSS variables, the audit should compare used custom properties against the generated set — not against a designer’s memory. The code below is a minimal Claude Code project hook sketch; adapt the command to your real audit script.
- File-scoped lint after Write/Edit: catch hardcoded colors in the touched file fast.
- Package audit on Stop: catch cross-file drift and missing token usage.
- Fail closed (exit 2 / deny): the agent must fix or explain before the turn ends.
- Human still owns taste: hooks catch drift and policy, not whether the hierarchy feels right.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "node scripts/design-file-lint.mjs"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "npm run agentic:audit"
}
]
}
]
}
}Section 04
Gate 2: accessibility in the loop, not after launch
Accessibility checks after merge are how teams accumulate debt. Agents make that worse because they can generate large surfaces quickly: unlabeled icon buttons, missing landmarks, focus rings removed “for cleanliness,” and contrast that fails WCAG on the paper background you actually ship. Put axe (or an equivalent CLI) on the stop gate for the routes the agent touched, and keep a cheaper static check on the write path for obvious anti-patterns (click handlers on non-buttons, images without alt in JSX, `outline-none` without a replacement focus style).
Evidence beats opinion. Prefer a Playwright-driven axe run against localhost for the affected routes, writing JSON to disk so the hook can fail on any serious violations. Feed the summary on stderr so the model sees countable issues, not a vague “check a11y.” If a full browser sweep is too slow for every stop, split the gates: PostToolUse runs eslint-plugin-jsx-a11y or a custom AST pass; Stop runs axe on a short allowlist of routes declared in the brief or in a `qa-routes.txt` file the agent is required to update.
Claude Code’s documented behavior matters here: a blocking `Stop` hook prevents the model from ending the turn and continues the conversation with the error feedback. That is exactly what you want when axe reports a P0 contrast failure on the membership CTA. Gemini’s synchronous hook model likewise lets you hold the loop until the check completes. Neither tool replaces a human accessibility review for complex interactions — but both can refuse the fiction that “done” includes known serious violations.
#!/usr/bin/env bash set -euo pipefail # Fail closed: any serious axe finding blocks Stop / AfterAgent. npx playwright test tests/a11y-smoke.spec.ts --reporter=line node scripts/summarize-axe.mjs --fail-on serious,critical
PostToolUse / AfterTool on Write
Stop / AfterAgent
PostToolUse / AfterTool
Stop / AfterAgent
PreToolUse / BeforeTool
Match the failure mode to the earliest hook that can catch it without burning the whole suite on every keystroke.
Section 05
A Gemini CLI twin for the same policy
Teams rarely standardize on one agent. The policy should be the scripts; the hooks should be thin adapters. Put `scripts/design-file-lint.mjs`, `npm run agentic:audit`, and `scripts/a11y-gate.sh` in the repo once. Then declare Claude and Gemini hook configs that call the same commands at the analogous lifecycle points. Gemini CLI documents hooks under `.gemini/settings.json` with matchers such as `write_file|replace` on `BeforeTool` / tool events, and agent-level hooks for post-response validation.
Start with one BeforeTool path deny for token sources, one after-write lint, and one after-agent package gate. Resist the urge to port twenty hooks on day one. A single fail-closed stop gate that runs your existing verify script already changes studio behavior: agents stop claiming victory while the audit is red, and humans stop arguing about whether the model “should have remembered” the brand rules.
Projects to inspect
{
"hooks": {
"BeforeTool": [
{
"matcher": "write_file|replace",
"hooks": [
{
"name": "protect-token-sources",
"type": "command",
"command": "$GEMINI_PROJECT_DIR/.gemini/hooks/deny-token-path.sh"
}
]
}
],
"AfterTool": [
{
"matcher": "write_file|replace",
"hooks": [
{
"name": "design-file-lint",
"type": "command",
"command": "node $GEMINI_PROJECT_DIR/scripts/design-file-lint.mjs"
}
]
}
],
"AfterAgent": [
{
"hooks": [
{
"name": "design-qa-gate",
"type": "command",
"command": "npm run agentic:audit && $GEMINI_PROJECT_DIR/scripts/a11y-gate.sh"
}
]
}
]
}
}Section 06
Studio practice: fail closed, review with eyes
Hooks are not a substitute for critique. They remove the cheap mistakes so review time can go to hierarchy, density, copy, and whether the page does the user job. Write the gate list into the brief: which routes must pass axe, which directories are token-audited, which files are off-limits. If the agent updates a route list as part of the task, the stop gate becomes honest evidence instead of a stale smoke test.
Measure the cost. A stop gate that adds ninety seconds is usually cheaper than a round of design QA that rediscovers purple buttons. A stop gate that re-runs the full suite on every turn will be disabled by the first impatient operator — and a disabled gate is worse than no gate because the team believes protection exists. Keep PostToolUse cheap; keep Stop thorough but scoped; keep PreToolUse reserved for policy denies.
Ship the pattern as team infrastructure. Commit hook configs and scripts. Document the exit-code contract in AGENTS.md in three lines. When someone asks why the agent will not stop, the answer should be a command output, not a vibe. That is what it means for design QA to become part of the control plane.
- Encode anti-slop and token rules in scripts the hooks call — not only in Markdown.
- Use PreToolUse/BeforeTool for path policy; PostToolUse/AfterTool for fast file lint; Stop/AfterAgent for package proof.
- Fail closed on serious a11y and token drift; leave taste judgments to humans.
- Share one script surface across Claude Code and Gemini CLI adapters.
Sources
Sources & further reading
- Claude Code hooks reference
Official reference for hook events, matchers, exit codes, and Stop blocking behavior.
- Automate actions with hooks (Claude Code)
Anthropic’s hooks quickstart and configuration guidance.
- How to configure hooks (Claude)
Anthropic overview of PreToolUse, PostToolUse, Stop, and common automation patterns.
- Gemini CLI hooks
Official Gemini CLI hooks introduction and settings precedence.
- Gemini CLI hooks reference
Technical reference for Gemini hook configuration and agent events.
- Tailor Gemini CLI to your workflow with hooks
Google Developers Blog announcement of Gemini CLI hooks as synchronous middleware.
- Playwright accessibility testing
Running axe-core checks inside Playwright for route-level accessibility gates.
- Style Dictionary
Token transform pipeline used to keep CSS variables aligned with DTCG sources.


