Home / Extending: skills, MCP, subagents, hooks, plugins
Hooks: automating the lifecycle
A hook is an action the harness runs itself when a precise lifecycle event occurs. An instruction written in memory remains a preference the model may forget; a hook declared once runs on every occurrence, independently of what the model recalls at that moment.
A hook is an action the harness executes itself when a precise lifecycle event occurs, never a request the model interprets at the moment of acting. Writing to a memory file every time you commit, running the tests, remains a preference: the model may forget it after a long conversation or a compaction. A hook declared on the corresponding event runs on every occurrence, independently of what the model recalls at that moment.
The mechanism: an event, a command, a decision
A hook is declared in settings.json, or in a plugin's hooks.json file, tied to a lifecycle event and an action type: a shell command, an HTTP call, an MCP tool, a prompt, or an agent. When the event occurs, the harness runs that action and waits for a decision in return, before the question reaches the model.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "./verifier-avant-commande.sh" }
]
}
]
}
}
This declaration ties the PreToolUse event, restricted to calls of the Bash tool, to a script that runs before every proposed command. Each hook type carries its own default timeout, different depending on the event that triggers it: the next lesson details these timeouts event by event, along with the precise JSON fields a hook returns to block, allow, or enrich what the model sees.
Hook versus memory rule: what each actually guarantees
A memory rule depends on a model that rereads it and decides to apply it, within a context window that grows over the course of a session. A hook depends on the harness, which checks the event independently of what the conversation holds at that moment. The difference shows up most on a long session: an instruction repeated twenty times in a memory file can dilute within a loaded context, whereas a hook declared once continues to run identically on the thousandth call as on the first.
A hook that refuses must say what to do
A rule that has cost you once is mechanized as a PreToolUse hook. The refusal arrives before the effect, and its message names the alternative: with exit code 2, what the script writes to standard error is the text Claude receives, and it corrects its call instead of going in circles.
#!/bin/sh
# refuses and says what to do instead
echo "Refused: do not delete, move the file to archives/." >&2
exit 2
By default, a hook that fails lets the action through: a wrong path or a crashing script, and everything passes. For a security hook, declare "onFailure": "block" on a command or HTTP hook, which also blocks the action when the hook cannot start, times out or exits with an unexpected code. To see which hooks exist, type /hooks: a single list, grouped by event. To know whether one ran, start claude --debug, which logs the command, plugin, outcome and duration of each command hook on tool calls, prompts, SessionStart and Stop when it finishes. A <system-reminder> tag written in a hook's output is neutralized before it reaches Claude: a hook's output cannot pass itself off as a system instruction.
From trigger to decision
Memory rule versus declared hook
| Making a repeated automatism reliable | What triggers the action | Behaviour on a long session | Where the automatism is declared |
|---|---|---|---|
| Rule written in memory | A reading by the model, which must remember it at the right moment | Can dilute after a compaction or a loaded context | A CLAUDE.md file, interpreted by the model |
| Declared hook | A lifecycle event, observed by the harness itself | Runs identically on every occurrence of the event | settings.json or a plugin's hooks.json, executed by the harness |
A developer declares a PreToolUse hook in settings.json that blocks any call to the Bash tool containing the pattern rm followed by -rf. He then asks Claude Code to run a command containing this pattern, and sees the hook trigger.
Write in one sentence what this situation establishes, and in one sentence what it does not establish.
What this establishes: This situation establishes that the hook does intercept a Bash command matching the declared pattern, at the moment the tool is about to run.
What this does not establish: It does not establish that the same hook will intercept a destructive command phrased differently, for example with separate options or a dynamically built path, since only the tested pattern was observed.
The three most common miscalibrations
- Too broad This situation proves that the hook now blocks every destructive command, whatever its phrasing.
- Too narrow This situation proves nothing, since a hook can never replace the vigilance of a developer who rereads every command.
- Beside the point This situation shows that the hook ran faster than a manual review of the command would have.
- A hook fires because a lifecycle event occurs, never because the model decides to apply it at the moment of acting.
- A rule repeated in a memory file can dilute within a loaded context, a hook declared once runs identically on every occurrence of the event.
- A hook is declared in settings.json or in a plugin's hooks.json file, tied to an action type among shell command, HTTP call, MCP tool, prompt, or agent.
- A hook that refuses names the alternative on its error output with exit code 2, and Claude corrects its call instead of retrying identically.
- A failing hook lets the action through by default: onFailure: "block" suits security hooks, /hooks lists them and --debug shows whether they ran.
Create a minimal .claude/settings.json file in a test repository, declare a PreToolUse hook there that prints a message before every Bash command so you can watch the mechanism trigger, then remove that hook once the demonstration is done.
Every datable claim in this lesson links here to the public text behind it. A source that does not open proves nothing.
- Claude Code, hooks: onFailure, exit code 2, /hooks consultée le 2026-10-24
- Claude Code changelog: /hooks, --debug, neutralized tags consultée le 2026-10-24