AI Computing · From my bench
Claude Code hooks: what they are, and the nervous system I built with them
Updated 19 July 2026 · about a 10 minute read
I've watched an AI coding agent retry the same broken fix five times. It could see each individual error perfectly well - what it couldn't see was the pattern of repeated failure. It read the same file three times in one session because context compaction had wiped its memory of reading it. No sense of time, no peripheral vision, no feeling of being stuck. Hooks are how you give it one.
This is a working explanation of Claude Code hooks - the events, the contract, a minimal example you can adapt - and then the thing I actually built with them: metacog, a two-hook nervous system published as @houtini/metacog.
What hooks are
Hooks are shell commands that Claude Code runs automatically at specific moments in a session. They're registered in your settings file, they fire without the agent asking, and they can inject text into the agent's context, block an action, or stay completely silent. That last property is the important one, and it's what separates hooks from MCP servers: an MCP tool only does anything when the model chooses to call it, while a hook watches everything and speaks only when it has something to say. Nothing to say means zero output, zero tokens, near-zero latency.
The events that matter
- PostToolUse - fires after every tool call (Read, Write, Bash, all of them). Monitoring, validation, side effects.
- UserPromptSubmit - fires when you send a message. Context injection, session setup.
- PreToolUse - fires before a tool runs. This one can block - the place for "never let it touch that directory".
- Stop - fires when the agent finishes responding. Cleanup and verification.
There are others, but in practice these four cover almost everything I've wanted to build.
The JSON contract
A hook receives its payload as JSON on stdin (which tool ran, with what input) and talks back to Claude Code via JSON on stdout:
{
"continue": true,
"suppressOutput": false,
"systemMessage": "This message appears in the agent's context"
}Output nothing and exit 0, and the hook is invisible. The systemMessage lands in the agent's context as a note it can act on - or ignore. Registration lives in ~/.claude/settings.json (global) or .claude/settings.json (per project), with a matcher to scope which tools trigger it:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{ "type": "command", "command": "node C:/hooks/after-write.js" }
]
}
]
}
}A minimal working hook
Here's the shape of a real one - a PostToolUse hook that nudges the agent when it edits source with no matching test file:
#!/usr/bin/env node
// after-write.js - warn the agent when it edits a file with no matching test.
const fs = require('fs');
const input = JSON.parse(fs.readFileSync(0, 'utf8')); // hook payload on stdin
const file = input.tool_input?.file_path ?? '';
if (/\/src\/.+\.ts$/.test(file)) {
const test = file.replace('/src/', '/tests/').replace('.ts', '.test.ts');
if (!fs.existsSync(test)) {
console.log(JSON.stringify({
systemMessage: `No test file exists for ${file}. Worth creating one before moving on?`,
}));
process.exit(0);
}
}
// Say nothing when everything's fine. Silence is the feature.
process.exit(0);Note what it doesn't do: it doesn't lecture on every write, and it doesn't block anything. A nudge the agent can weigh against its own judgement beats a gate it'll fight. And it says nothing at all in the normal case - a hook that talks constantly is a hook the agent learns to skim past, at your token expense.
What I built: metacog
Once you see the primitive, the question is what deserves building. My answer came from watching agents fail: they can't feel when they're stuck. So metacog is a pair of hooks that gives the agent something like a nervous system. I say "something like" because the signals arrive as text in context, not sensation - closer to a colleague leaving a post-it note than biological proprioception. It turns out that's enough.
One hook fires after every tool call and watches for failure patterns. The other fires on your messages and injects rules learned from past sessions. When everything's fine, both are silent. When something's off, a short signal appears - at first just awareness, the agent's own reasoning deciding what to do with it. If the agent keeps failing, the signals escalate to structured interventions:
[NOCICEPTIVE INTERRUPT] You have attempted 4 similar fixes with consecutive similar errors. Before taking another action: 1. State the assumption you are currently operating on 2. Describe what read-only action would falsify that assumption 3. Execute that investigation before writing any more code
The escalation matters, because if the agent's reasoning were working properly it wouldn't be stuck in the first place. Install is one command - npx @houtini/metacog --install - and it's open source, zero dependencies.
The seven senses
Each sense is a cheap calculation over the recent tool-call history, compared against a baseline:
| Sense | What it detects |
|---|---|
| O2 | Context trend - token velocity spikes, context being consumed unsustainably |
| Chronos | Temporal awareness - time and step count since the user last spoke |
| Nociception | Error friction - repeated similar errors, the agent is stuck |
| Spatial | Blast radius - file dependency count after writes |
| Vestibular | Action diversity - repeated identical actions, going in circles |
| Echo | Validation bias - writing code without running the project’s tests |
| Drift | Scope drift - chasing dependency chains instead of the original task |
Rules that strengthen on silence
The part I find most interesting is what happens when a problem resolves. Metacog extracts what changed and persists it as a behavioural rule, injected into future sessions. Most cross-session memory uses time-decay: the rule works, the failure stops, the system sees silence and prunes the rule, the agent forgets, the behaviour regresses. Metacog inverts it - if a rule was active, its preconditions were met, and the failure didn't happen, that's evidence the rule is working. Rules get stronger when they succeed, not stale.
Rules for writing your own
- Silence is the feature. Design for the hook to say nothing on 95% of events. Every message costs tokens and attention.
- Be fast. The hook spawns on every matched event. Milliseconds, not seconds - no network calls in a PostToolUse hook if you can help it.
- Nudge before you block. PreToolUse gates are for properly dangerous actions. Everything else works better as context the agent can weigh.
- Escalate on evidence. One odd event is noise. Four similar errors is a pattern worth interrupting for.
Hooks are the other half of the delegation story I wrote up in my vLLM setup: that piece is about giving the orchestrator capable workers, this one's about giving it self-awareness. The stack's better with both.