← Claude Code Hub
✦ Tip #203 Sep 20, 2026

Prompt hooks in Claude Code: when the rule needs judgment, not an exit code

Some rules a script cannot check, because they are not a condition, they are a judgment call. A prompt hook puts a fast model in charge of that decision, on the event you pick.

Two ways a hook can answer: type command returns an exit code from a script, type prompt sends the condition to a fast model that answers ok false with a reason, and a strip reminds you the evaluator reads the transcript, not your files

TL;DR A "type": "prompt" hook sends your condition plus the event JSON to a fast model, which answers {"ok": true|false, "reason": "..."}. It covers rules no script can check. Three things to settle before you write one: the evaluator reads the conversation, not your repo; on PreToolUse you need continueOnBlock or the turn dies on the first block; and unless you tell it when to return impossible, it can keep you in a loop.

A command hook answers with an exit code. That covers anything a script can decide: whether the file exists, whether the linter passed, whether the command matches a pattern.

What it does not cover are the rules that are a judgment call. "Don't stop if you left tests red." "Don't run that command if it destroys real data." Writing that in bash turns into a pile of grep that breaks on the first case you did not foresee. There is another hook type for this, and you may have used it already without building one: /goal is a prompt hook on Stop, packaged for one session and one case. Writing your own gives you the whole primitive: any of the thirteen events that accept it, permanent in settings.json, and four fields /goal never exposes.

How it works

Instead of spawning a process, Claude Code sends your prompt to a fast model (Haiku by default) along with the event JSON, which lands wherever you put $ARGUMENTS. The model replies with a fixed JSON shape: ok, reason and optionally impossible. One call, no tools.

That sets the boundary: the evaluator reads the conversation transcript, not your project. If the condition cannot be checked from what Claude has already surfaced in the chat, it cannot be judged. And when the conversation runs long, the transcript is trimmed to a budget and the evaluator is told to answer insufficient evidence in transcript when the part it needs fell off the front (verified in the 2.1.278 binary). Keep conditions short and measurable.

Setting one up

1. The minimum block

In ~/.claude/settings.json or the project's .claude/settings.json:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Context: $ARGUMENTS\n\nDid it run the test suite and finish green? Answer {\"ok\": true, \"reason\": \"...\"} if the transcript proves it, or {\"ok\": false, \"reason\": \"what is missing\"} if not.",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

On Stop, an ok: false hands your reason back to Claude as its next instruction and the turn carries on. Same cycle as /goal, but across every session and without restating it each time.

2. Give it a way out or you get a loop

impossible is not a config field, it is part of the model's response. The evaluator can return it on its own, but the way to get it when you need it is to ask for it in the prompt:

If the condition can never be satisfied (the file does not exist, the task does not
apply), answer {"ok": false, "reason": "...", "impossible": true}.

With impossible: true, Claude Code lets the turn end instead of feeding the reason back. Without that sentence, an unsatisfiable condition gets re-evaluated turn after turn.

3. On PreToolUse, continueOnBlock is not optional

The same hook on PreToolUse becomes a semantic guard: it judges the command before it runs. The default flips the outcome there. With continueOnBlock at false (the default), an ok: false ends the turn and the reason shows up as a warning line. With true, the reason goes back to Claude as the tool error and it keeps working, which is what you usually want:

{
  "type": "prompt",
  "prompt": "Proposed command: $ARGUMENTS\n\nDoes it delete or overwrite data that cannot be recovered? {\"ok\": false, \"reason\": \"...\"} if so.",
  "continueOnBlock": true
}

To filter before the model is called at all, the same entry takes an if field holding a permission rule: conditional hooks.

What validation catches, and what it misses

A malformed prompt hook does not raise an error, it disappears. Ship it inside a plugin and claude plugin validate does say so, in these words:

❯ hooks: hooks.Stop.0.hooks.0: Invalid prompt hook (prompt: Invalid input);
  entry ignored at runtime

What it does not catch: a prompt hook parked on an event that does not accept one. With a prompt hook placed on SessionStart, validation passes clean, no warning. The thirteen events that accept prompt are PreToolUse, PostToolUse, PostToolUseFailure, PostToolBatch, PermissionRequest, PermissionDenied, Stop, SubagentStop, TaskCreated, TaskCompleted, TeammateIdle, UserPromptSubmit and UserPromptExpansion. On SessionStart, PreCompact or Notification you still need a command hook.

prompt or agent

If the condition needs to open files or run the test suite, the type is not prompt but agent: same shape, except it spawns a subagent with tools and up to 50 turns. It is experimental and its default timeout goes up to 60 seconds. The working rule: prompt judges what is already in the conversation, agent goes and checks.

Reference

Field Where Value
prompt config Required. $ARGUMENTS receives the event JSON; leave it out and the JSON is appended
model config Optional. Defaults to the small fast model (Haiku)
timeout config Optional. 30 seconds
continueOnBlock config Optional. false by default: blocking ends the turn
ok response true allows, false blocks
reason response Required when ok is false
impossible response With ok: false, on Stop and SubagentStop it lets the turn end

Official docs: Hooks reference

Free guide

The 51 essentials, as a guide.

One page per tip. Five chapters. What I actually use daily in production. No theory, no fluff.

  • I. Getting started 10 tips
  • II. Awareness 3 tips
  • III. Mastery 22 tips
  • IV. Autonomy 10 tips
  • V. Comparison 6 tips
Are you a professional Web developer?

You'll receive the guide by email · You join the Gravitas newsletter · Unsubscribe anytime

of 51
#

Wmedia · 51 Tips
Free guide · 51 tips · 5 chapters

The 51 essentials, as a guide.

Are you a professional Web developer? · Unsubscribe anytime
Workshop for teams

Multiply your team's output without sacrificing quality: a 6 to 8 hour AI First workshop, online, on the Claude platform.

See the workshop

Want the 51 Claude Code essentials as a guide?