Tier 4
Tier 4 — Enforce: Write the rules into the harness, so you stop reminding and approving
| You stop | You do instead | Climb when |
|---|---|---|
| Reminding and approving | Write the rules | One agent is too slow, or a big build floods its context |
Reach for this tier when you give the agent the same correction again, or your prompt still says "remember to run the tests." A request can be forgotten; a hook can't. Recurring feedback becomes code: every correction you repeat, and every approval you click through, is a rule waiting to be written into the harness.
These are the triad's constraints, hardened — from a line in the prompt into hooks, CI, and permissions the harness enforces for you.
The harness around the loop — hooks that always run your tests, CI, the gh CLI — makes the whole loop deterministic and shareable with the team.
4.1 Replace "remember to run tests" with a hook.
Instead of: "run the tests after each change" (a request it can forget).
Prefer: a hook that always runs them, and one that won't let it stop on red.
This is where Tier 3 becomes durable: the check you defined there gets wired into the harness, so it runs every time and can't be skipped or forgotten.
How: .claude/settings.json (commit it so the team shares the gate):
{
"hooks": {
"PostToolUse": [
{ "matcher": "Edit|Write",
"hooks": [{ "type": "command",
"command": "FILE=$(cat | jq -r '.tool_input.file_path // empty'); [ -n \"$FILE\" ] && npm test -- --findRelatedTests \"$FILE\"" }] }
],
"Stop": [
{ "hooks": [{ "type": "command",
"command": "INPUT=$(cat); [ \"$(echo \"$INPUT\" | jq -r .stop_hook_active)\" = true ] && exit 0; npm test || { echo 'tests still failing' >&2; exit 2; }" }] }
]
}
}exit 2 on a Stop hook forces the agent to keep working (guard with stop_hook_active so it can't loop forever — Claude overrides a Stop hook after 8 consecutive blocks). PreToolUse exit 2 blocks a tool outright — use it to protect .env, package-lock.json, .git/; a PreToolUse deny fires before the permission check, so it holds even under --dangerously-skip-permissions (hooks can tighten, never loosen). Hooks aren't only shell + exit codes: beyond command, a hook can be a prompt (a quick Haiku yes/no judgment) or an agent (a subagent that verifies before allowing stop) — an agent-based verify hook is a sturdier loop check than a brittle test-runner line.
4.2 Move repetitive engineering into CI / headless — run it without you in the seat.
Instead of: doing PR review, issue triage, and release notes by hand each time.
Prefer: run the agent non-interactively in your pipeline, scoped tightly.
How:
claude -p "Review this PR diff for correctness and security; comment only on real issues." \
--allowedTools "Bash(git diff:*),Read,Grep"Or the Claude Code GitHub Action on pull_request. Note: as of mid-2026, programmatic use (claude -p, Agent SDK, Actions) bills from a separate Agent-SDK pool at API rates — budget for it.
4.3 Let Claude drive gh — the collaboration layer is harness too.
Instead of: copy-pasting diffs into the GitHub web UI.
Prefer: have Claude use the
ghCLI to open PRs, read issues, check CI, and answer review comments.
How:
gh issue view 123 # pull the task context into the session
gh pr create --fill # open the PR from the working branch
gh pr checks # watch CI; feed failures back into the loop
gh pr comment 45 --body "addressed in 3f9a1c2"Claude has native git (stage/commit/branch/PR) and can run gh. In CI, claude -p + gh closes the loop from issue → branch → PR → review.
4.4 Turn a correction you keep giving into a rule that tells the agent the fix.
Instead of: typing "use the logger, not
console.log" into the chat for the third time this week.Prefer: a lint rule or a hook that blocks the mistake and says how to fix it: "No
console.loginsrc/— uselogger.debug()from@/lib/logger."
The failure message is the prompt. When the check fails, the agent reads why and makes the fix on its own; a bare "lint failed" sends it guessing. Every correction you write down is one you never give again.
How: the linter you already run is the cheapest home, and most let you write the message:
// eslint.config.js — in your config object
rules: {
'no-restricted-properties': ['error', {
object: 'console', property: 'log',
message: "Use logger.debug() from '@/lib/logger' instead.",
}],
},For what no linter can see, use a PreToolUse hook (Tip 4.1): exit 2 blocks the action, and whatever it prints to stderr is the correction the agent reads.
4.5 Put approvals in rules, not prompts — a prompt you always approve has stopped being a check.
Instead of: clicking "approve" on every
npm test,git diff, and file edit, one prompt at a time.Prefer: write the decisions you keep making into
.claude/settings.json—allowthe safe commands,denythe dangerous ones — so the prompts that still reach you are the ones worth reading.
Anthropic's own telemetry showed Claude Code users approving roughly 93% of permission prompts — and the more approvals someone sees, the less attention they pay to each (How we contain Claude across products). An approval given on reflex protects nothing. A rule written once, and committed so the team shares it, holds on every run.
How: .claude/settings.json:
{
"permissions": {
"allow": ["Bash(npm test:*)", "Bash(npm run lint)", "Bash(git diff:*)", "Bash(git status)"],
"deny": ["Bash(git push --force:*)", "Bash(rm -rf:*)", "Read(./.env)", "Read(./.env.*)"]
}
}Deny wins over allow. For the commands that must never run, back the deny list with a PreToolUse hook (Tip 4.1) — it holds even when permissions are skipped.