Hooks and Settings — wiring external behavior into tool execution
Hooks and Settings — wiring external behavior into tool execution
There are places where you want external behavior to run before or after an LLM agent's tool call. Auto-formatter execution, change watching, logging, policy validation, secret-leak blocking — that kind of work.
1. About Claude Code Hooks
Claude Code provides a hook system. At defined points in the model's tool-call lifecycle, it runs user-defined shell commands. The places fit policy blocking, automation, and external notifications.
Frequently mentioned events:
| Event | When |
|---|---|
PreToolUse |
Right before tool execution. |
PostToolUse |
Right after tool execution. |
UserPromptSubmit |
Before user input is passed to the model. |
Stop |
When the model finishes its response. |
SessionStart |
When a session starts. |
Notification |
At notification points like tool use. |
A hook is defined as a shell command, takes JSON input, and returns its outcome via exit code and stdout. Exit code 0 passes; any other value blocks or warns.
2. The settings.json hierarchy
| Layer | Location |
|---|---|
| User global | ~/.claude/settings.json |
| Project (shared) | <project>/.claude/settings.json |
| Project (personal) | <project>/.claude/settings.local.json |
| Enterprise | OS- and organization-specific location. |
When the same key exists at multiple layers, they merge by a defined precedence.
3. Permission model
{
"permissions": {
"allow": ["Bash(git status)", "Read"],
"ask": ["Bash(*)"],
"deny": ["Bash(rm -rf *)"]
}
}
allow— auto-allowed.ask— ask the user on call.deny— blocked.
Tool/argument pattern matching is supported. Too broad (e.g., Bash(*) auto-allowed) weakens security; too narrow piles up "ask" fatigue.
4. Hook definition
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "scripts/check-bash.sh" }]
}],
"PostToolUse": [{
"matcher": "Edit",
"hooks": [{ "type": "command", "command": "scripts/format.sh" }]
}]
}
}
The script reads the event's JSON input from stdin, processes it, and returns an exit code. The exact input/output schema and matching syntax are safest to follow from the official docs verbatim.
Environment variables — settings or hooks can add or change environment variables. Avoid keeping credentials in plain-text files; combine with OS secret managers or external environment-variable loading.
5. Similar concepts in other tools
Git pre-commit · pre-push hooks — managed via .git/hooks/ or tools like husky · lefthook · pre-commit. The difference is being tied to git actions.
husky — a library frequently used for managing git hooks in the Node ecosystem.
pre-commit (Python · cross-language) — a polyglot git-hook tool. Combines with various lint/format tools.
lint-staged — runs lint and format only on staged files. Often combined with husky.
Cursor · Continue — other AI coding tools are gradually adding options to run external commands before/after work.
CI-side guard — checks at the PR stage in GitHub Actions, GitLab CI, and so on. The shape where CI catches what hooks miss.
6. Examples of guard scripts
Frequently mentioned hook places:
- Block secret leaks — block code changes that contain
.envor credential patterns. - Block destructive commands — irreversible commands like
rm -rfor DBDROP. - Auto-format — prettier, ruff, gofmt, clang-format after
Edit. - Auto-run tests — invoke unit tests when specific directories change.
- Notifications — desktop notifications and messenger messages on completion.
7. Portability · debugging
Portability:
- Windows · Unix —
.cmd/.ps1on Windows,.shon Unix. Match shebang (#!/usr/bin/env bash) and permissions (chmod +x). - PATH — commands invoked by the hook (
prettier·ruff) must be on PATH. Care needed with venvs and nvm. - Encoding — when Windows console encoding mismatches script output encoding, you get garbled output.
Debugging:
- Check where the tool's logs put hook script stdout/stderr.
- Use options like
set -eat the start and clear error messages. - Verify the meaning of exit codes before deciding pass/block.
8. Common pitfalls
Infinite hook loops — the hook calls a tool that re-triggers the hook. Narrow the matching condition.
Accidentally allowing every command — the mistake of putting Bash(*) in allow. The permission scope balloons.
Untrusted hook sources — taking someone else's settings as-is runs arbitrary commands in your environment. Apply only after code review.
Logging sensitive info — when hooks log command arguments and file contents, secrets leak. Mask and filter.
Ignoring OS differences — leaving Unix-only shell scripts as-is fails on Windows.
Layer-precedence confusion — when user, project, and enterprise settings merge, it's unclear which wins.
Missing block message — leaving a block result without a reason makes debugging hard. Provide friendly errors.
Version compatibility — hook event names and input schemas may change. Verify behavior on tool updates.
Closing thoughts
Hooks are powerful for auto-format, secret-leak blocking, and policy validation. That said, broadening permissions weakens security, and taking settings from untrusted sources can run arbitrary commands. For small operations, two layers — git-side hooks plus CI-side guards — are sufficient.
Next
- claude-md-pattern
- ai-coding-ides
We refer to Claude Code Settings · Claude Code Hooks · Git githooks · pre-commit · husky · lint-staged · GitHub Actions.