Hooks - The Event System
Created: March 29, 2026 | Modified: March 29, 2026
This is Part 10 of a 10-part series on cAgents. Previous: Sessions - Under the Hood | First: Getting Started with cAgents
The previous article covered sessions - the static record of what happened. This article covers hooks - the live machinery that runs while a pipeline is executing.
Hooks are how cAgents reacts to events in real time: tracking which agents are running, catching dangerous commands before they execute, saving state before context gets compacted, and coordinating teams of parallel agents. Sessions record what happened. Hooks make it happen.
Claude Code fires events at specific moments - when a session starts, when a tool is about to be used, when a subagent spawns, when context gets compacted. cAgents registers hook scripts that run in response to these events. It's an event-driven architecture that keeps the orchestration layer informed without requiring the agents themselves to report back.
How Hooks Work
Hooks are configured in settings.json. Each hook maps an event type to a script. When Claude Code fires the event, it runs the script and passes context via stdin as JSON. The script can return instructions that modify Claude Code's behavior - blocking a tool call, injecting context, or logging state.
Here's what a hook registration looks like:
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -c '... node \"$R/.claude/hooks/run-hook.cjs\" bash-validator'",
"timeout": 5
}
]
}
]
Three things to notice:
- matcher filters which tool triggers the hook. This one only fires when a Bash command is about to run. Other matchers target Write, Edit, Task, or any tool name.
- timeout prevents hung hooks from blocking the pipeline. Five seconds is typical - hooks should be fast.
- run-hook.cjs is a launcher that dispatches to the right hook script. The argument (
bash-validator) tells it which hook to run.
Event Types
cAgents hooks into every major event type Claude Code exposes. Here's the full map, grouped by when they fire.
Session Lifecycle
| Event | When It Fires | cAgents Hook |
|---|---|---|
| SessionStart | Session begins | session-catchup.cjs - restores state from incomplete sessions |
| SessionEnd | Session ends | team-stop.cjs - cleans up team processes |
| Stop | Agent stops | verify-completion.cjs - checks execution_summary.yaml exists |
| StopFailure | Agent fails to stop | stop-failure-handler.cjs - saves recovery state |
Agent Lifecycle
| Event | When It Fires | cAgents Hook |
|---|---|---|
| SubagentStart | Subagent spawns | subagent-tracker.cjs - registers in agent_tree.yaml |
| SubagentStop | Subagent finishes | subagent-stop-tracker.cjs - records completion summary |
| TeammateIdle | Team member idle | teammate-idle-handler.cjs - reassigns or stops idle teammates |
| TaskCompleted | Task finishes | team-task-complete.cjs - triggers next wave |
Tool Guards
| Event | When It Fires | cAgents Hook |
|---|---|---|
| PreToolUse (Bash) | Before shell command | bash-validator.cjs - blocks dangerous commands |
| PreToolUse (Write/Edit) | Before file write | secret-detection.cjs - prevents committing secrets |
| PreToolUse (Write/Edit/Bash) | Before mutation | attention-injection.cjs - injects context reminders |
| PreToolUse (Write/Edit/Bash) | Before mutation | approval-gate.cjs - enforces approval for sensitive ops |
| PreToolUse (Task) | Before agent spawn | session-init-gate.cjs - ensures session exists first |
| PreToolUse (Task) | Before agent spawn | model-routing-advisor.cjs - recommends optimal model |
| PostToolUse (Write/Edit) | After file write | post-write-validator.cjs - validates written content |
| PostToolUseFailure | After tool fails | tool-failure-tracker.cjs - logs failures for self-correction |
Context Management
| Event | When It Fires | cAgents Hook |
|---|---|---|
| PreCompact | Before context compression | pre-compact-save.cjs - saves workflow state to disk |
| PostCompact | After context compression | post-compact-restore.cjs - restores critical context |
| InstructionsLoaded | Instructions loaded | instructions-loaded.cjs - validates instruction integrity |
User Interaction
| Event | When It Fires | cAgents Hook |
|---|---|---|
| UserPromptSubmit | User sends message | delegation-enforcer.cjs - prevents /run from self-handling |
| UserPromptSubmit | User sends message | magic-keywords.cjs - detects special command patterns |
| PermissionRequest | Permission needed | permission-handler.cjs - auto-grants safe permissions |
| Notification | Notification fired | notification.cjs - routes notifications to correct session |
That's over 20 hooks across 15+ event types. Each one is a small, focused script that does one thing - but together they form the coordination layer that makes multi-agent pipelines work reliably.
Key Hooks in Detail
Five hooks that do the most important work:
subagent-tracker.cjs
Every time a subagent spawns, this hook writes an entry to agent_tree.yaml in the session directory. It captures the agent's ID, type, parent relationship, role, and spawn timestamp. When the agent finishes, the companion hook subagent-stop-tracker.cjs appends its completion summary.
This is the hook that builds the audit trail from Part 9. Without it, the session would know what was requested and what state the pipeline reached, but not which agents did the work or when they ran. Every entry in agent_tree.yaml exists because this hook wrote it.
pre-compact-save.cjs and post-compact-restore.cjs
Long pipelines can exceed Claude Code's context window. When that happens, Claude Code compresses the context - summarizing earlier conversation to make room for new work. That compression is useful, but it can lose critical state: which task is in progress, what's been completed, what files were changed, what the plan says to do next.
pre-compact-save.cjs fires before compression and writes the current workflow state to the session directory - plan progress, active tasks, file change list, and any in-flight coordination data. After compression, post-compact-restore.cjs reads that state back and injects the critical bits into the fresh context.
This pair is why pipelines can survive context compression without losing their place. The agent might not remember the full conversation history, 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 - blocking destructive operations like rm -rf /, sudo commands that could modify system state, and other patterns that could damage the host system or the project.
The safety net matters because agents run shell commands frequently - installing dependencies, running builds, checking file state, executing tests. Most of those commands are fine. The validator's job is to catch the rare ones that aren't, before they execute. It's a PreToolUse hook, so it can block the command entirely and return an error message to the agent explaining why.
delegation-enforcer.cjs
This one enforces the architectural pattern that makes cAgents work. The /run pipeline is supposed to delegate work to specialist agents - planners plan, executors execute, validators validate. But the pipeline agent itself is a capable Claude instance. Without guardrails, it might decide to skip delegation and just write the code directly.
delegation-enforcer.cjs catches this. It monitors UserPromptSubmit events and checks whether /run is attempting to do work itself instead of spawning agents. If it detects self-handling, it intervenes with a reminder to delegate. It's the architectural enforcement that keeps the multi-agent pattern honest.
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 summary is missing, the pipeline ended abnormally: it was interrupted, errored out, or the agent stopped without finishing.
The hook flags incomplete sessions so you know to investigate or resume them. If it fires, just ask Claude "what happened with that last run?" - it can read the session files, diagnose what went wrong, and resume from where things stopped. Without this hook, a silently failed pipeline could look like it simply finished.
The Launcher Pattern
You might have noticed that the hook registration points to run-hook.cjs with the actual hook name as an argument, rather than pointing directly to each hook script. This is deliberate.
Instead of registering 20+ separate commands in settings.json - each with its own path, timeout, and configuration - cAgents uses a single launcher script that takes the hook name as an argument and dispatches to the right .cjs file. The launcher handles common setup (reading stdin JSON, loading the session context, error handling) so individual hooks can focus on their specific logic.
This keeps settings.json clean and makes it easy to add new hooks. Write a new .cjs file, add one line to the launcher's dispatch table, and register one entry in settings.json. The alternative - 20+ separate command entries with duplicated paths and timeouts - would be a maintenance headache.
Wrapping Up the Series
The hook system is what turns cAgents from a collection of agents into an actual orchestration framework. Sessions record what happened. Hooks make it happen in real time - tracking, guarding, saving, restoring, and coordinating across every event in the pipeline.
This is the final article in the series. You've gone from installation through every command and now understand 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
Series navigation: Previous: Sessions - Under the Hood | First: Getting Started with cAgents