cc-create-hook
Skill0 starsChanged 5 months agoMITView on GitHub
Create a project-level Claude Code hook. Use whenever the user wants to add, set up, wire up, or configure a hook — to run a script before or after a tool, validate or block a command, auto-run a linter or formatter on edits, load context at session start, or react to any Claude Code lifecycle event. Writes the hook config into .claude/settings.json and the script into .claude/hooks/. Trigger this even if the user just describes the behavior (e.g. "run prettier every time you edit a file") without saying the word "hook".
- Deletes or force-pushes
- Commits and pushes
In this repository3 other files it gives its agents.
- cc-create-ruleSkill
- grill-meSkill
- prompt-improverSkill
The file
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 0 are distinctive, and 5 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 53
Start from this template (.claude/hooks/<name>.sh):
- Very specific: line 126
Important: if .claude/settings.json already exists, read it first and merge — don't overwrite it.
- Very specific: line 133
Run /hooks in Claude Code to see it listed (read-only browser — confirms it loaded and from which file).
- Very specific: line 135
If it "does nothing", check: is the script executable?
- Very specific: line 143
Prefer PostToolUse for automation (format/lint/test) and PreToolUse for guardrails (block/validate).
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Create a Claude Code hook
A hook is a script that fires automatically at a fixed point in Claude Code's lifecycle. Two pieces, always:
- The wiring goes in
.claude/settings.json(project-level, safe to commit). - The script goes in
.claude/hooks/.
Work through the 5 steps below in order. Ask the user only what you can't already infer.
Step 1 — Pick the event (when should it fire?)
Match the user's goal to one event. These five cover almost everything:
| Want to… | Event | Can it block? |
| Run a linter/formatter/test after a file changes | PostToolUse | no (observe) |
| Block or validate a command/edit before it runs | PreToolUse | yes |
| Add context or vet the prompt the user just sent | UserPromptSubmit | yes |
| Load context / set up env when a session starts | SessionStart | no |
| Run a check when Claude finishes a turn | Stop | yes |
Need something else (subagents, compaction, notifications, file watching, MCP, etc.)? See references/events.md for the full list of ~30 events.
Step 2 — Pick the matcher (what narrows it?)
The matcher filters when the event fires. What it matches depends on the event:
- Tool events (
PreToolUse,PostToolUse) match on tool name:"Bash"— only Bash commands"Edit|Write"— file edits or writes (pipe = "or")"mcp__.*"— any MCP tool (this one's a regex)"*"or omitted — everything
SessionStartmatches onstartup,resume,clear, orcompact.UserPromptSubmitandStoptake no matcher — they always fire.
To narrow a tool event further (e.g. only git push, only .ts files), add an if field to the handler in Step 4 using permission-rule syntax: "Bash(git push *)", "Edit(*.ts)". One rule per if; no &&/||.
Step 3 — Write the script in .claude/hooks/
Claude Code sends the event as JSON on stdin. Read it, do the work, then signal the result with an exit code:
exit 0→ success. (ForPreToolUse/Stop/UserPromptSubmit, this means "no objection".)exit 2→ block it. Whatever you print to stderr is shown to Claude as the reason.- any other code → non-blocking error, just logged.
Start from this template (.claude/hooks/<name>.sh):
#!/bin/bash
# Reads the event JSON from stdin.
input=$(cat)
# Pull out the field you care about (needs jq on PATH).
# For tool events, the tool's args live under .tool_input:
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')
command=$(echo "$input" | jq -r '.tool_input.command // empty')
# --- do your thing here ---
exit 0Blocking example — a PreToolUse hook that refuses rm -rf:
#!/bin/bash
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command // empty')
if echo "$command" | grep -q 'rm -rf'; then
echo "Blocked: rm -rf is not allowed here." >&2
exit 2
fi
exit 0Auto-format example — a PostToolUse hook that runs prettier on the edited file:
#!/bin/bash
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')
[ -n "$file_path" ] && npx prettier --write "$file_path"
exit 0Make it executable:
chmod +x .claude/hooks/<name>.shNeed finer control than exit codes (allow without prompting, rewrite the tool input, add context for Claude)? Print a JSON object to stdout on
exit 0instead. The schemas are inreferences/events.md.
Step 4 — Wire it into .claude/settings.json
The config nests three levels: event → matcher group → handlers. Use the ${CLAUDE_PROJECT_DIR} placeholder so the path works no matter where Claude is running, and pass args: [] (this runs the script directly, no quoting headaches).
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/<name>.sh",
"args": []
}
]
}
]
}
}Important: if .claude/settings.json already exists, read it first and merge — don't overwrite it. Add your event key (or append to the array under an existing event key). If the file doesn't exist, create it with just the hooks block above.
Optional handler fields worth knowing: "if" (narrow filter, Step 2), "timeout" (seconds, default 600), "async": true (run in background, don't block Claude).
Step 5 — Verify
Tell the user:
- Run
/hooksin Claude Code to see it listed (read-only browser — confirms it loaded and from which file). - Edits to settings are picked up automatically; no restart needed.
- If it "does nothing", check: is the script executable? is
jqinstalled? does the matcher actually match the tool? Exit code 1 does not block — onlyexit 2does.
Keep it minimal
- One hook = one job. Don't cram multiple checks into one script; add separate handlers.
- Reach for a hook only when something must happen deterministically at a lifecycle point. For "Claude should usually do X", a rule or CLAUDE.md is lighter.
- Prefer
PostToolUsefor automation (format/lint/test) andPreToolUsefor guardrails (block/validate).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

