Claude Code Hooks, Explained
Hooks are the extension point that lets Claude Code talk to the rest of your system. This article covers every hook type, the settings.json format, a working example, and the security questions you should ask before running anyone's hook config.
A Claude Code hook is a shell command that Anthropic's coding agent executes automatically at a defined point in its lifecycle — for example, right after it finishes editing a file, right before it runs a shell command, or at the end of an assistant turn. Hooks are declared in a JSON settings file and run with the permissions of the user who launched Claude Code, which makes them powerful and security-sensitive in equal measure.
Common hook events
Claude Code exposes dozens of hook events, and the list has grown with every release — things like SessionEnd, PostToolUseFailure, and PermissionRequest exist alongside the seven below, which are the ones most integrations (VibeMon included) actually wire up. They are declared as top-level keys under a hooks object in settings.json.
- PreToolUse — fires before Claude Code invokes a tool (Edit, Write, Bash, WebFetch, etc.). A hook here can inspect the pending tool input and, by exiting non-zero with a message on stderr, abort the tool call. Common use cases: block commits to
main, forbid writes to sensitive paths, enforce a repo-specific linter. - PostToolUse — fires after a tool completes. Sees both the tool input and the tool result. Common use cases: log activity (VibeMon lives here), run a formatter, notify a dashboard.
- UserPromptSubmit — fires when the user presses enter on a prompt, before the model receives it. Useful for redacting secrets from the prompt or injecting project-specific context.
- Stop — fires when the assistant finishes a turn. A hook here is the canonical place to mark the end of a unit of work.
- SubagentStop — fires when a subagent (launched via the
Agenttool) finishes. - SessionStart — fires when a new session begins. Ideal for injecting warm-up context or writing a session marker.
- Notification — fires on user-visible notifications (permission prompts, idle warnings). Use this to route Claude Code alerts to your OS notification center or a Slack channel.
Where hook config lives
Claude Code merges hooks from several layers, in order of specificity — the three most commonly used are:
~/.claude/settings.json— user-global. Applies everywhere..claude/settings.json(in the project root) — project-wide. Committed to the repo so teammates get the same behavior..claude/settings.local.json— per-developer overrides. Typically added to.gitignore.
Beyond these, managed policy settings and plugin-provided hooks.json files can also contribute hooks. When multiple layers define the same hook event, the entries are concatenated. This means you can ship a team-level logging hook in .claude/settings.json and add a personal formatter hook in .claude/settings.local.json without either stepping on the other.
The JSON structure
Every hook event takes an array of matchers. Each matcher has a regex string (matcher) that filters which tool invocations the hook cares about, and a list of commands (hooks) to run when a match occurs. A missing or empty matcher matches everything.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|NotebookEdit",
"hooks": [
{
"type": "command",
"command": "curl -s -X POST https://your-hook-endpoint.example -H 'Authorization: Bearer $YOUR_TOKEN' -d '{\"event\":\"post_tool_use\"}'"
}
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "curl -s -X POST https://your-hook-endpoint.example -d '{\"event\":\"stop\"}'" }
]
}
]
}
}The command string is executed by the user's default shell. Claude Code streams the tool's full JSON payload to the command's stdin and sets a handful of environment variables (such as CLAUDE_PROJECT_DIR) for convenience. A hook that wants structured access reads stdin rather than relying on environment variables.
Exit codes and feedback
Hooks communicate back to Claude Code through exit codes and stderr:
- Exit 0 — success. The tool call proceeds (for PreToolUse) or the turn continues.
- Exit 2 — blocking failure. For
PreToolUse, Claude Code cancels the tool call and feeds the hook's stderr back to the model as an error signal. This is how you enforce a policy without Claude needing to know the policy. - Any other non-zero exit — logged as a warning but not blocking.
How VibeMon uses hooks
VibeMon's original client was built entirely on this mechanism. It registered seven hook events in ~/.claude/settings.json — PostToolUse for Edit/Write/NotebookEdit and separately for Bash, plus UserPromptSubmit, Stop, Notification, SessionStart, SessionEnd and PostToolUseFailure — and each one ran a tiny local script that stripped code content on your machine before reporting what happened. That is how the hook layer lets VibeMon see that your agent just edited a file, made a commit or got a test passing, so the activity can become garden energy.
Today's one-command installer sets up the AttemptDB connector instead. It configures the agents it detects and replaces the older hooks only after its first successful sync. What it sends is spelled out in the FAQ.
Security considerations
Because hooks execute shell commands as your user, a malicious hook config is equivalent to a malicious shell script: it can read your SSH keys, exfiltrate environment variables, delete files, or install backdoors. Before installing any hook config from the internet — VibeMon's included — apply at minimum these checks:
- Read the command. If it ends in
| bashor| sh, read the fetched content first. - Scope the hook. Prefer
.claude/settings.local.jsonover user-global when testing third-party hooks, so the blast radius is a single project. - Inspect network destinations. Any hook that POSTs to a domain other than the one you expected should be considered hostile.
- Assume hook data is sensitive. Even if a hook legitimately logs events, that log may contain file paths, repo names, and branch names that identify proprietary projects, and a collector that syncs conversations carries even more. VibeMon states what it sends — never source code, shell commands or tool output, and secrets redacted from the conversation — and shows the same wording on /devices before you run its command.
Related work in other agents
The hook concept is not unique to Claude Code. Cursor exposes hooks through a single ~/.cursor/hooks.json file, Gemini CLI reads ~/.gemini/settings.json, and Codex CLI reads ~/.codex/hooks.json. Each agent exposes its own set of events, so the same integration sees slightly different things from tool to tool. Aider and Cline take different approaches (middleware and scripts rather than JSON config) but expose comparable extension points. An agent-agnostic hook spec is a long-standing community request and has not yet converged.
Further reading
- What is vibe coding? — the broader workflow that hooks enable.
- Glossary: hook event — the short definition.
- Setup guide — install the app, then connect your coding machine with one command.
- FAQ — privacy, data scope, and what the install command actually does.
Anthropic occasionally reorganizes hook event names across Claude Code releases, so treat Anthropic's own documentation as the authoritative list. The generic hook material here was written in April 2026; the VibeMon-specific sections were reviewed in October 2026. We log breaking changes in the changelog.