Set up a guardrail: hooks
Chapter objectives
- Understand what a hook is and when it fires
- Distinguish the 3 types of hooks
- Build a quality gate that blocks non-compliant publications
The risk: publishing a mistake
Your skill writes and publishes. But what happens if a post contains an em dash you hate, exceeds the platform's character limit, or goes out to Instagram without an image? Right now, nothing stops it. You could proofread every post by hand — but then why automate at all? You could add "double-check before publishing" to the skill — but an instruction in a prompt remains probabilistic: the model follows it almost always, and "almost" isn't good enough when the mistake is public and instant.
You need a guardrail of a different nature: a check that runs systematically, that Claude can neither forget nor bypass. That's exactly what hooks are.
What is a hook?
A hook runs code at key moments of Claude Code's lifecycle. It fires automatically: you don't have to remember to call it, and Claude doesn't have to remember to respect it — it's imposed on it. A hook can format files after a modification, block a command before it runs, inject context at session start, or notify you when a task finishes.
The trigger moments (the "events") cover the whole lifecycle: before a tool runs (the PreToolUse event — the one we care about for blocking a publication), after it runs (PostToolUse — perfect for reformatting a modified file), when you submit your message, at session start, or when Claude finishes its reply. Each hook is tied to an event and, optionally, a filter (for example: only Bash commands containing "publish").
The three ways to validate
The selection rule: take the least powerful one that suffices. Checking a character limit with an agent hook is bringing a bulldozer to plant a lettuce — slow, expensive, and more fragile. Our quality gate combines simple, objective rules: a command hook is the right tool.
Where hooks live
Hooks are configured in your settings files (.claude/settings.json to share them with the project, or settings.local.json for yourself only). The structure ties together an event, a filter ("matcher"), and the command to run. Here's the general shape of a hook that fires before Bash commands:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node .claude/hooks/quality-gate.js"
}
]
}
]
}
}The blocking mechanism is elegantly simple: the hook's script receives the action's details as input, and its exit code decides what happens next. Exit 0: all good, the action proceeds. Exit 2: the action is blocked, and whatever the script wrote to stderr is sent back to Claude — which reads it and fixes the issue. So the hook doesn't just say no: it explains why, and Claude uses that explanation to repair.
Building the quality gate
We create a hook that fires before any publication and checks the content. If it fails, the command is blocked and Claude sees precisely what to fix:
create a command hook that fires before any Bash publishing command. the hook checks the post's content: - em dashes (to be replaced with "...") - exceeding the platform's character limit - missing media for platforms that require one - banned words from my brand voice guide if a check fails: block the command and show precisely what to fix.
flowchart TD
P["Publication attempt"] --> H{"Hook: quality gate"}
H -->|"Compliant"| OK["Published"]
H -->|"Non-compliant"| KO["Blocked + list of fixes"]
KO --> F["Claude fixes the content"]
F --> PNotice the loop in the diagram: blocked doesn't mean dead. Claude reads the error message, fixes the content, and retries — most often without any intervention from you. The guardrail doesn't slow the system down: it makes it self-correcting.
Testing the hook (by forcing it to fail)
An untested guardrail is a decorative guardrail. The method: deliberately trigger every failure type and verify the block. It's the same reflex as a developer who tests their error cases, not just their happy path:
test the hook on a tweet that exceeds the character limit test the hook on an instagram post without an image test the hook on a post containing the word "revolutionary"
For each test, the output must show that the action was blocked ("validation failed") with the exact reason. If a case passes when it should block, now is when you find out — not the day a non-compliant post goes live. Perfect: your pipeline will never publish non-compliant content.
Beyond the quality gate
Once you've grasped the mechanism, hooks become a reflex for everything that must be systematic. A few classic uses: automatically reformatting every modified file (PostToolUse), forbidding any command touching a sensitive folder, loading the project's state at session start, or sending a notification when a long task finishes. The question to ask is always the same: "do I want this to happen every time, without depending on anyone's memory?" If yes, it's a hook.
For Lea, this chapter changes the nature of her system: before, the automation was fast but required her proofreading; now it's fast and trustworthy. That's what will make the scaling of the next two chapters possible — you only parallelize what you've first secured.
Context
Lea has banned the word "revolutionary" from her communication and always requires an image on Instagram — two non-negotiable rules she never wants to check by hand again. You set up the guardrail, then play the role of the mean tester: your goal is to get a non-compliant post through. If you can't, the guardrail is good.
Instructions
- Create the quality gate with the prompt provided in the chapter.
- Open the generated configuration in
.claude/settings.jsonand spot the event, the matcher, and the script being called. - Request an Instagram post without an image and verify it's blocked with an explicit message.
- Generate a post containing "revolutionary" and verify the block + the correction message.
- Force a Twitter character-limit overflow and watch the loop: block → fix by Claude → retry.
- Verify that a perfectly compliant post goes through without friction.
- Add a rule of your own (for example: ban multiple exclamation marks) and retest.
In summary
- A prompt instruction is probabilistic; a hook is deterministic: it runs every time, no exceptions.
- Hooks attach to lifecycle events: before a tool (PreToolUse), after (PostToolUse), at session start…
- 3 types: command (shell script), prompt (LLM decision), agent (multi-step validation) — take the least powerful that suffices.
- The configuration lives in
.claude/settings.json; an exit code of 2 blocks the action and sends the explanation back to Claude. - The quality gate blocks non-compliant publications and makes the pipeline self-correcting: Claude reads the error and repairs.
- Test a hook by deliberately triggering every failure — an untested guardrail is decorative.
- It applies to all your skills with no extra configuration, and it will also apply to the next chapter's subagents.
Quiz — check your understanding
1. What's the main characteristic of a hook?
2. Which hook type is the simplest for validating text?
3. Why a hook rather than a "check before publishing" instruction in the skill?
4. How does a command hook block an action?
5. Which event do you use to check content BEFORE it's published?