Hooks — The Event System
A session directory is the static record of what happened. A hook is the live machinery that runs while the pipeline is still moving — because the session can only describe what already finished.
Hooks are how cAgents reacts to events in real time: tracking which subagents are running, catching dangerous commands before they execute, saving state before the context window compacts, and coordinating teams of parallel agents. Sessions record. Hooks intervene.
Hooks themselves are a Claude Code primitive — Claude Code fires events at specific moments and runs whatever script you registered against each event. cAgents adds a launcher convention and roughly twenty scripts on top, but the underlying mechanism is Claude Code's. The full schema, payload shape, and event list live at the canonical reference (see Hooks in the official docs); Part 5 of the Claude Code guide walks through authoring one from scratch. This article is about what cAgents does with the primitive, not how the primitive itself works.
How a hook fires
Hooks are configured in .claude/settings.json. Each entry maps an event to a shell command, with an optional matcher that filters which tool triggers it. Claude Code pipes the event payload to stdin as JSON; the script reads it, does its one focused thing, and exits inside its timeout (5 seconds is the cAgents convention).
cAgents does not register each script directly. It registers a single launcher and passes the hook name as an argument:
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -c '... node \"$R/.claude/hooks/run-hook.cjs\" bash-validator'",
"timeout": 5
}
]
}
]
The matcher narrows the trigger — this entry only fires on Bash. The timeout caps how long the hook can take before Claude Code gives up on it. The launcher (run-hook.cjs) dispatches to the right script under .claude/hooks/<name>.cjs. The prompts panel on the right walks you through wiring one of these into your own project end to end — the article frames the primitive, the panel is the reproducible build.
.cjs files via a launcher is a cAgents convention, not a Claude Code requirement. Claude Code will run any executable command. The launcher pattern keeps settings.json short when twenty hooks share the same setup; for a one-off project hook, registering a script directly is simpler.Where cAgents listens
cAgents wires a hook against most of the events Claude Code exposes. The full event list and payload schema is canonical Anthropic territory (see Hooks in the official docs); the table below is the cAgents-specific column — which script runs at each event, and what it does.
Session and agent lifecycle
| Event | cAgents hook | What it does |
|---|---|---|
| SessionStart | session-catchup.cjs | Restores state from incomplete sessions |
| SessionEnd | team-stop.cjs | Cleans up team processes |
| Stop | verify-completion.cjs | Checks that execution_summary.yaml was written |
| StopFailure | stop-failure-handler.cjs | Saves recovery state |
| SubagentStart | subagent-tracker.cjs | Registers the agent in agent_tree.yaml |
| SubagentStop | subagent-stop-tracker.cjs | Records completion summary |
| TeammateIdle | teammate-idle-handler.cjs | Reassigns or stops idle teammates |
| TaskCompleted | team-task-complete.cjs | Triggers the next wave |
Tool guards (PreToolUse / PostToolUse)
| Matcher | cAgents hook | What it does |
|---|---|---|
| Bash | bash-validator.cjs | Blocks dangerous shell commands |
| Write | Edit | secret-detection.cjs | Prevents committing secrets |
| Write | Edit | Bash | attention-injection.cjs | Injects context reminders |
| Write | Edit | Bash | approval-gate.cjs | Enforces approval for sensitive ops |
| Task | session-init-gate.cjs | Ensures a session exists before any agent spawns |
| Task | model-routing-advisor.cjs | Recommends the optimal model for the spawn |
| Write | Edit (post) | post-write-validator.cjs | Validates written content |
| any (failure) | tool-failure-tracker.cjs | Logs failures for self-correction |
Context management and user interaction
| Event | cAgents hook | What it does |
|---|---|---|
| PreCompact | pre-compact-save.cjs | Saves workflow state before compression |
| PostCompact | post-compact-restore.cjs | Restores critical context after compression |
| InstructionsLoaded | instructions-loaded.cjs | Validates instruction integrity |
| UserPromptSubmit | delegation-enforcer.cjs | Prevents /run from doing work itself instead of delegating |
| UserPromptSubmit | magic-keywords.cjs | Detects special command patterns |
| PermissionRequest | permission-handler.cjs | Auto-grants safe permissions |
| Notification | notification.cjs | Routes notifications to the correct session |
Twenty-plus small scripts, each focused on one job. The coordination layer that makes multi-agent pipelines reliable is the sum of them, not any single one.
Which hooks do the most work
Five worth reading top to bottom:
subagent-tracker.cjs
Every time a subagent spawns, this hook writes an entry to agent_tree.yaml in the session directory — id, type, parent, role, spawn timestamp. When the agent finishes, subagent-stop-tracker.cjs appends its completion summary alongside.
This is the hook that builds the audit trail Part 9 describes. Without it, the session would know what was requested and what state the pipeline reached, but not which agents did the work or in what order. Every entry in agent_tree.yaml exists because this hook wrote it.
pre-compact-save.cjs and post-compact-restore.cjs
Long pipelines push the conversation toward compaction — the model summarises older turns to free room for new ones. Compaction is useful, but lossy: which task is in progress, what's been completed, what files changed, what the plan says to do next can all blur together in the recap.
pre-compact-save.cjs fires before compression and writes the current workflow state to disk — plan progress, active tasks, file change list, in-flight coordination data. After compression, post-compact-restore.cjs reads that state back and re-injects the critical bits. The agent might not remember the full conversation, but it knows exactly where it is in the plan and what to do next.
bash-validator.cjs
This hook intercepts every Bash command before execution. It checks the command against a set of safety rules and blocks destructive operations — rm -rf /, sudo invocations that mutate system state, the patterns that would damage the host or the project.
Agents run shell commands constantly: installing dependencies, running builds, checking file state, executing tests. Most are fine. The validator's job is to catch the rare ones that aren't. It's a PreToolUse hook, so it can return an error to the agent explaining why the call was blocked rather than letting the command go through.
delegation-enforcer.cjs
This one enforces the architectural pattern that makes cAgents work. The /run pipeline is supposed to delegate — planners plan, executors execute, validators validate. The pipeline agent itself is a capable Claude instance, though, so without guardrails it can quietly skip the handoff and write the code directly — collapsing the pipeline back into one-shot prompting.
delegation-enforcer.cjs watches UserPromptSubmit events and intervenes the moment /run starts self-handling. It's the architectural enforcement that keeps the multi-agent pattern honest under pressure.
verify-completion.cjs
This hook fires when an agent stops and checks whether execution_summary.yaml was written to the session directory. A properly completed pipeline always writes a summary — it's the last step before VALIDATED. If the file is missing, the pipeline ended abnormally: it was interrupted, errored out, or the agent stopped without finishing.
The hook flags the incomplete session so you know to investigate or resume. Just ask Claude what happened with that last run? and it can read the session files, diagnose the failure, and pick up where things stopped. Without this hook, a silently failed pipeline looks the same as a finished one.
Why one launcher beats twenty registrations
You may have noticed the registration above points at run-hook.cjs with the actual hook name as an argument, rather than at each script directly. That's deliberate.
Twenty separate command entries in settings.json would mean twenty paths, twenty timeouts, twenty places to keep convention in sync. The launcher is one command. It reads stdin, loads the session context, picks the right .cjs file from its dispatch table, and runs it. Adding a hook becomes: write a new .cjs, add a line to the dispatch table, register one more launcher entry. Nothing more.
For a project that doesn't already have the launcher wired, registering a script directly is the right pick — it's one less moving part. The launcher pays for itself once you have more than a handful of hooks, not before. The prompts panel covers both shapes and which to choose.
.claude/settings.json. Plugin-supplied hooks appear alongside your own; you can add custom hooks against the same event names without disturbing the cAgents ones.When you're done
The hook system is what turns cAgents from a collection of agents into an actual orchestration framework. Sessions record. Hooks track, guard, save, restore, and coordinate across every event in the pipeline.
This is the final article in the series. You've gone from installation through every command, ending at the infrastructure underneath. If you want to dig deeper, the cAgents source is open. The full series:
Part 1: Getting Started | Part 2: /designer | Part 3: /run | Part 4: /team | Part 5: /org | Part 6: /optimize | Part 7: /debug | Part 8: /review | Part 9: Sessions | Part 10: Hooks