Hooks and Automation
Deterministic lifecycle hooks for Claude Code and CI/CD integration.
Last updated
Workflow1. Lifecycle Events
Claude Code exposes 26 lifecycle events that fire at specific points during a session. Hooks subscribe to these events and run deterministic logic -- shell scripts, HTTP calls, or LLM evaluations -- before the agent proceeds.
Key events
| Event | When it fires | Use case |
|---|---|---|
PreToolUse | Before a tool executes | Block or modify tool input |
PostToolUse | After a tool returns | Validate output, enforce invariants, keep feedback short |
Stop | Before final response | Quality gate on the answer |
SessionStart | Session begins | Check prerequisites, inject only minimal context |
PermissionRequest | Tool needs approval | Auto-approve or deny based on rules |
SubagentStart | Subagent spawns | Limit concurrency, log subagent work |
SubagentStop | Subagent finishes | Aggregate results, check quality |
PreCompact | Before context compaction | Preserve critical context |
PostCompact | After context compaction | Re-inject lost context |
CwdChanged | Working directory changes | Update project-specific config |
FileChanged | File is written/modified | Lint, format, or trigger rebuild |
Other events include TaskCreated, TaskCompleted, ConfigChange, WorktreeCreate, WorktreeRemove, and Elicitation. Most projects only need PreToolUse, PostToolUse, and Stop to start.
2. Handler Types
Each hook specifies one of five handler types. The type determines how the hook runs and what it can do.
| Type | How it runs | Best for |
|---|---|---|
command | Shell script (deterministic) | Blocking patterns, file checks, git guards |
http | POST to an endpoint | External services, logging, webhooks |
mcp_tool | Call an MCP server tool | Reuse existing MCP integrations |
prompt | Single-turn LLM eval (Haiku) | Semantic judgment, fuzzy matching |
agent | Multi-turn subagent | Deep verification, complex checks |
Exit code protocol
Hooks communicate decisions through exit codes. Exit 0 means proceed normally. Exit 2 means block the action -- the hook's stderr becomes feedback the agent sees and can act on. Keep that feedback factual and non-procedural. Any other exit code is treated as an error.
For PreToolUse hooks, the handler can also return JSON on stdout with permissionDecision (to auto-approve or deny) and updatedInput (to modify tool arguments before execution).
Example: block force-push
# .claude/hooks/no-force-push.sh
# PreToolUse hook matching the Bash tool
INPUT=$(cat -)
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
if echo "$CMD" | grep -qE 'git\s+push\s+.*--force'; then
echo "Blocked: force-push is not allowed." >&2
exit 2
fi
exit 0
Register it in .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"type": "command",
"command": ".claude/hooks/no-force-push.sh"
}
]
}
}
3. The Dispatcher Pattern
A single event can trigger multiple hooks. When a project grows past a handful, concurrent hooks reading from stdin can corrupt each other's JSON input. The dispatcher pattern solves this: one hook per event reads stdin once, caches it, then runs all handlers sequentially from the cached copy.
How it works
- A single
commandhook is registered for each event (e.g.,pre-tool-dispatch.sh). - The dispatcher reads the full JSON payload from stdin into a variable.
- It iterates through a config file listing all handlers for that event, piping the cached JSON to each one.
- If any handler exits
2, the dispatcher propagates the block immediately.
Config-driven thresholds
Store thresholds and feature flags in JSON config files, not hardcoded in scripts. This lets you adjust behavior (max file size, allowed commands, blocked patterns) without editing hook code.
4. Skills and Slash Commands
Skills are Markdown files (.claude/skills/deploy/SKILL.md) that define reusable capabilities Claude can invoke. They supersede the older slash-command system and load on demand, keeping the context window lean.
Invocation control
| Frontmatter field | Effect |
|---|---|
disable-model-invocation: true | User-only -- Claude cannot trigger this skill autonomously |
user-invocable: false | Model-only -- the user cannot call it directly |
Dynamic context injection
Skills can inject live data into their prompt using !command syntax. When the skill loads, the command runs and its stdout replaces the directive inline. This keeps skills context-aware without hardcoding values.
Key frontmatter fields
name-- display name for the skilldescription-- what triggers the skill (matched semantically)allowed-tools-- restrict which tools the skill can usemodel-- override the default modeleffort-- reasoning effort levelcontext-- files or globs to auto-loadhooks-- lifecycle hooks specific to this skill
5. CI/CD Integration
Any CI pipeline that runs shell commands can run Claude Code as a headless agent using the -p flag (pipe mode). The agent reads a prompt from stdin or arguments, executes it non-interactively, and exits with a status code.
Trust spectrum
| Level | What the agent does | Human involvement |
|---|---|---|
| Assist | Comments on PRs with suggestions | Reviewers apply manually |
| Draft | Opens PRs with proposed changes | Human reviews and merges |
| Auto-merge | Merges if CI passes | Human sets merge criteria |
| Autonomous | Full lifecycle: detect, fix, ship | Post-hoc audit only |
Start at Draft. Move to Auto-merge only for low-risk, high-frequency tasks (dependency bumps, doc regeneration) where the CI suite provides sufficient coverage.
Common patterns
- Dependency updates -- bump, run tests, open PR
- Doc generation -- regenerate API docs from code changes
- Code review assistance -- post inline comments on PRs
- Bug triage -- label and assign issues based on stack traces