Create Hook
InstructionClaude
Slash command to create and configure Claude Code hooks with reference documentation and interactive guidance
- 💡 Use Case:
- Use when setting up automated workflows like ESLint auto-fixing, prettier formatting, test runners, or commit message validation
Prompt Content
description: Create a new Claude Code hook in a settings file argument-hint: [event] [matcher] [command]
Add a hook to a Claude Code settings file. Hooks are shell commands the harness runs automatically at fixed points in Claude’s lifecycle (before a tool runs, after a file edit, when a session starts, and so on). They live in a settings file under the hooks key, not in .claude/agents/ or .claude/commands/.
Inputs:
$1— EVENT (one of the hook events below, e.g.,PostToolUse)$2— MATCHER (tool name or regex, e.g.,Edit|Write; use""for events without matchers)$3— COMMAND (shell command to run, e.g.,npx prettier --write "$CLAUDE_PROJECT_DIR")--user— Write to~/.claude/settings.json(applies to all projects)--local— Write to.claude/settings.local.json(this project, not committed)- Default — Write to
.claude/settings.json(this project, shared with the team) --timeout— Optional timeout in seconds for the command
Outputs:
STATUS=<CREATED|EXISTS|FAIL> PATH=<settings file> EVENT=<event> MATCHER=<matcher>
| Event | When it runs | Matcher |
|---|---|---|
PreToolUse | Before a tool call (can block it) | Tool name, e.g. Bash, Edit|Write, mcp__.* |
PermissionRequest | When a permission dialog would be shown | Tool name |
PostToolUse | After a tool call succeeds | Tool name |
Notification | When Claude Code sends a notification | Notification type (optional) |
UserPromptSubmit | When the user submits a prompt | None |
Stop | When the main agent finishes responding | None |
SubagentStop | When a subagent finishes | None |
PreCompact | Before context compaction | manual or auto |
SessionStart | When a session starts or resumes | startup, resume, clear, compact |
SessionEnd | When a session ends | None |
-
Validate inputs:
- EVENT must be one of the events in the table above (case-sensitive)
- MATCHER is only meaningful for events that support it; omit it otherwise
- COMMAND must be non-empty
- If
--timeoutis given, it must be a positive integer
-
Determine the settings file:
- Default:
.claude/settings.json - With
--local:.claude/settings.local.json - With
--user:~/.claude/settings.json - Create the directory and file (
{}) if they don’t exist
- Default:
-
Read and merge, never overwrite:
- Parse the existing JSON and keep every other key (
permissions,env, existing hooks, …) - Find
hooks.{EVENT}; create it as an empty array if missing - Reuse the entry whose
matcherequals MATCHER, or append a new one - If an identical
commandalready exists under that matcher, outputSTATUS=EXISTSand stop
- Parse the existing JSON and keep every other key (
-
Hook configuration shape:
{ "hooks": { "{EVENT}": [ { "matcher": "{MATCHER}", "hooks": [ { "type": "command", "command": "{COMMAND}", "timeout": 60 } ] } ] } }- Omit
matcherfor events that don’t use one - Omit
timeoutunless--timeoutwas given
- Omit
-
If the command needs a script:
- Put it in
.claude/hooks/{name}.sh(project) or~/.claude/hooks/{name}.sh(user) and make it executable - Reference project scripts with
"$CLAUDE_PROJECT_DIR"/.claude/hooks/{name}.shso it works from any working directory - The hook receives JSON on stdin (
session_id,hook_event_name,tool_name,tool_input, …); read it withjq - Exit code
0= success; exit code2= blocking error (stderr goes back to Claude, and forPreToolUsethe tool call is blocked); other codes = non-blocking error shown to the user
- Put it in
-
Write and report:
- Write the file back as formatted JSON (2-space indent) and confirm it still parses
- Print:
STATUS=CREATED PATH={settings file} EVENT={event} MATCHER={matcher} - Remind the user to review it with
/hooks; hooks edited on disk are picked up after review or on the next session
/create-hook PostToolUse "Edit|Write" 'jq -r ".tool_input.file_path" | xargs npx prettier --write'
# Output: STATUS=CREATED PATH=.claude/settings.json EVENT=PostToolUse MATCHER=Edit|Write
Resulting .claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r \".tool_input.file_path\" | xargs npx prettier --write"
}
]
}
]
}
}
/create-hook PostToolUse "Edit|Write" 'jq -r ".tool_input.file_path" | grep -E "\.(ts|tsx|js|vue)$" | xargs -r npx eslint --fix'
/create-hook PreToolUse "Bash" '"$CLAUDE_PROJECT_DIR"/.claude/hooks/check-commit-msg.sh'
.claude/hooks/check-commit-msg.sh:
#!/usr/bin/env bash
cmd=$(jq -r '.tool_input.command // ""')
if [[ "$cmd" == *"git commit"* ]] && ! grep -qE -- '-m "(feat|fix|docs|chore|refactor|test)(\(.+\))?: ' <<<"$cmd"; then
echo "Commit message must follow Conventional Commits (feat:, fix:, ...)" >&2
exit 2
fi
exit 0
/create-hook Stop "" 'npm test --silent' --user --timeout 300
# Output: STATUS=CREATED PATH=~/.claude/settings.json EVENT=Stop MATCHER=
- Idempotent: won’t add a duplicate command under the same event and matcher
- Never overwrites or reorders unrelated settings
- Output must be valid JSON
- No network access required
- Hooks run with your user permissions, automatically, without asking
- Quote shell variables (
"$VAR") and validate paths fromtool_input - Skip sensitive files (
.env,.git/, keys) in hooks that read or write files - Review hooks from other people’s repos before trusting them
/hooks- Interactive interface to view, add, and remove hooks- Hooks reference - Full list of events, input fields, and output options