Professional Agentic Product Engineering

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 gh CLI 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.log in src/ — use logger.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 — allow the safe commands, deny the 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.