Instructions or a Hook — Which Runs Every Time? CLAUDE.md vs Hooks

July 24, 2026 · Claude Certified Architect (CCA) Prep (part 26)

▶ Watch on YouTube & subscribe to The Stack Underflow

You add a line to CLAUDE.md: “Always run the linter after editing a file.” It works — for a while. Then a long session drifts, the model gets absorbed in a multi-file refactor, and three edits go by with no lint run. Nothing crashed. Nothing errored. The model simply didn’t get around to it, because a line in a markdown file was never a command. It was a suggestion, and suggestions compete with everything else in context for the model’s attention.

This is the exact trap Domain 3 of the Claude Certified Architect exam is built to test: not whether you know CLAUDE.md exists, but whether you know which mechanisms in Claude Code are advisory and which are enforced. Get that distinction wrong in production and “the agent usually remembers” becomes an incident report.

The one-sentence version: A CLAUDE.md instruction is context the model reads and may choose to act on; a hook is code the harness runs automatically at a lifecycle event, whether the model wants it to or not — so “guaranteed, every time” always means a hook.

The question

You need the linter to run after every file edit, guaranteed, without the agent deciding whether to bother. What mechanism gets you there?

  • A) A line in CLAUDE.md asking it to
  • B) A hook (PostToolUse on Edit/Write)
  • C) A slash command
  • D) A bigger model

Pause here and pick an answer before you scroll past the next heading.

The answer

B — a hook, specifically PostToolUse matched on the Edit and Write tools.

The mechanism is a lifecycle split. Claude Code’s agent loop has moments where your code, not the model, gets to intervene — PreToolUse before a tool runs, PostToolUse right after it finishes, SessionStart, Stop, and others. A hook registered on one of these events is invoked by the harness itself, deterministically, every time that event fires. The model has no vote. It doesn’t see the hook coming, it can’t argue with it, and it can’t simply forget to trigger it the way it can forget a line of prose buried three paragraphs into its context.

LANE 1 — CLAUDE.md instruction (advisory)
──────────────────────────────────────────
  File edited


  Model re-reads context, including "run the linter" line


  Model DECIDES whether to comply this turn

      ├── busy with other work → skips it → (amber, sometimes skipped)
      └── remembers            → runs it   → sometimes works


LANE 2 — PostToolUse hook (enforced)
──────────────────────────────────────────
  File edited (Edit or Write tool call completes)


  Harness fires PostToolUse event — matcher: Edit|Write


  Hook command runs: `eslint --fix "$FILE_PATH"` (or equivalent)


  Runs. Every time. No model reasoning in this path. (green, guaranteed)

The matcher field is what makes this specific to “after every file edit” rather than every tool call — you scope it to Edit and Write (and MultiEdit, if your codebase edit tool includes it), and the harness fires the hook only on those. A minimal .claude/settings.json entry looks like this:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "handler": {
          "type": "command",
          "command": ".claude/hooks/run-linter.sh"
        }
      }
    ]
  }
}

That configuration is the durable part of the answer — the mechanism. The exact flag names and JSON shape are worth re-verifying against the current Claude Code docs before you rely on them in a real project or walk into the exam room, since D3 tracks live tooling that moves fast. What won’t change is the underlying split: instructions are read and interpreted; hooks are invoked and executed.

Why the other options fail

  • A) A line in CLAUDE.md asking it to. This is the distractor the whole question is built around, and it’s tempting precisely because it often works. CLAUDE.md is real, persistent, shared context — but it’s still just tokens the model reads and reasons over like any other instruction. Under a long session, a busy turn, or a model that judges the edit “too trivial to lint,” the instruction gets silently skipped. Advisory means probabilistic, and “guaranteed” in the question stem rules out anything probabilistic.
  • C) A slash command. A slash command (.claude/commands/lint.md) is a shortcut you invoke by typing /lint — it does nothing unless a human or the model explicitly runs it that turn. It’s not tied to the Edit/Write event at all, so it can’t fire automatically “after every file edit.” It solves a different problem: reusable, on-demand prompts, not automatic enforcement.
  • D) A bigger model. A stronger model is more likely to remember and follow the instruction more of the time — but “more likely” is still not “guaranteed.” Model capability changes the odds of compliance with an advisory rule; it doesn’t convert an advisory rule into an enforced one. This option tests whether you conflate “better reasoning” with “deterministic execution,” which are unrelated axes.

The concept behind it

Zoom out past this one question and there’s a general principle worth internalizing: every control surface in an agentic system sits somewhere on a spectrum from advisory to enforced, and picking the wrong point on that spectrum is one of the most common production failure modes in agent design.

Control surfaceWho evaluates itEnforced?Best for
CLAUDE.md (project or user)The model, as contextNo — advisoryConventions, style, “how we do things here”
System prompt / instructionsThe modelNo — advisoryGeneral guidance, tone, priorities
Tool descriptionThe model, during tool selectionNo — advisory (shapes intent, not outcome)Nudging correct tool choice
Slash commandYou (or the model), on demandN/A — manual triggerReusable prompts you explicitly invoke
Hook (PreToolUse, PostToolUse, etc.)Your code, automaticallyYes — deterministicHard rules: lint every edit, block a dangerous command, log every call
Permission rule (allow/ask/deny)The harness, before executionYes — deterministicGating specific tools or commands outright

The pattern generalizes into a simple diagnostic question you can ask about any “make sure X always happens” requirement: does this rule need to survive the model changing its mind? If the answer is no — if it’s fine for the model to weigh it against other priorities — put it in context (CLAUDE.md, a system prompt, a tool description). If the answer is yes — if a skipped instance is a real incident, not just a missed nicety — it has to be enforced by code outside the model’s reasoning: a hook, or a permission rule.

This is also why hooks are described as a backstop, not a replacement for good prompting. A well-written CLAUDE.md still carries most of the day-to-day judgment calls efficiently and cheaply. Hooks exist for the minority of rules where “usually” isn’t good enough — a linter that must run on every commit, a destructive command that must never execute without confirmation, an audit log that must capture every tool call for compliance. Use context for guidance, use hooks for guarantees, and don’t confuse a model that got smarter with a rule that got enforced.

One more wrinkle worth knowing for the exam and for real use: a PostToolUse hook, as in this question, fires after the tool has already run — it’s the right place for “always lint,” “always log,” “always format.” If the requirement were “block the edit before it happens under some condition,” that’s a PreToolUse hook instead, since only PreToolUse runs early enough to stop the tool call outright. Matching the event to the timing your rule actually needs is a second-order version of the same skill this question is testing.

FAQ

Can a hook call the model, or is it always plain code? Both exist. The most common handler type is command — a shell script or executable, which is what “deterministic” usually means in practice. But Claude Code also supports a prompt handler type that routes the decision through a structured Claude call for cases needing judgment a plain if statement can’t express. Either way, the hook fires deterministically on the event; only the internal logic of a prompt-type hook involves the model.

If CLAUDE.md instructions can be skipped, why use CLAUDE.md at all? Because most rules don’t need a guarantee — they need context. Coding conventions, architectural preferences, “we use tabs not spaces” — these are cheap to express in prose and the model follows them the overwhelming majority of the time. Reaching for a hook on every single preference would be enforcement overkill and adds real engineering overhead. Reserve hooks for the subset of rules where a skip is a genuine incident.

Does a hook run even if the model tries to avoid triggering it? Yes, and that’s the entire point. A PostToolUse hook matched on Edit/Write fires whenever an Edit or Write tool call completes — the model doesn’t choose whether the hook runs, only whether it calls Edit or Write in the first place. It can’t call the tool “quietly” to dodge the hook; the harness, not the model, owns the event.

Where do I configure hooks — CLAUDE.md or somewhere else? Hooks are configured in settings.json, not CLAUDE.md — project-scoped at .claude/settings.json, or user-scoped at ~/.claude/settings.json to apply across every project you touch. This is itself a small D3 trap: CLAUDE.md is memory/context, settings.json is configuration/behavior, and hooks belong firmly in the second bucket.

Not affiliated with, authorized, or endorsed by Anthropic. “Claude” and “Claude Certified Architect” are trademarks of Anthropic. These are original practice questions for study, not real exam content — verify current exam details on Anthropic’s official pages.

Where this fits in the CCA series

This question sits in Domain 3 — Claude Code Configuration & Workflows (20% of the exam), the domain that rewards people who actually run Claude Code day to day over people who only read about it. If you haven’t yet, start with the exam guide for how all five domains fit together on one diagram. Two close siblings round out the “where does a rule live” cluster: Where Does a Team Convention Live? Project CLAUDE.md Explained covers the memory hierarchy this question’s advisory lane comes from, and A Rule for You, Not Your Team: Local vs Shared Claude Code Settings covers the settings-file side of the config layer where hooks themselves get registered. Once you’ve got instructions-versus-hooks straight, A Scoped ‘Test-Writer’ Agent: Custom Subagents in Claude Code is the natural next stop — subagents are a third control surface with its own scope rules. Browse all tutorials for the full series.

Found this useful? The deep version lives on YouTube — new breakdowns of how AI dev tools actually work, weekly.

Subscribe on YouTube →