| name | create-hook |
| description | Scaffold a Claude Code hook (command or HTTP) for this project. Use when you want to automate quality checks, formatting, or notifications on tool events. |
| disable-model-invocation | true |
| argument-hint | [hook description] |
| effort | medium |
Analyse the project, suggest useful hooks, then create and validate the one the user selects.
$ARGUMENTS is optional โ pass a description of what you want the hook to do (e.g. "type-check after every edit") to skip the suggestion step.
Step 1 โ Detect tooling and suggest hooks
Scan the project root for these indicators and propose relevant hooks:
| Detected | Suggested hook |
|---|
tsconfig.json | PostToolUse โ type-check .ts/.tsx files after each edit; surface errors as additionalContext so Claude auto-fixes |
.prettierrc / prettier.config.* | PostToolUse โ auto-format the edited file after each Write/Edit |
.eslintrc.* / eslint.config.* | PostToolUse โ lint + auto-fix the edited file after each Write/Edit |
package.json with test script | PreToolUse (Bash) โ run tests before any git commit command |
.git/ directory | PreToolUse (Bash) โ scan staged files for secrets/API keys before commit |
| Any project | PostToolUse โ block writes to protected directories (migrations/, generated/) |
If $ARGUMENTS was provided, skip suggestions and go directly to Step 2 using the described purpose.
Otherwise: list the suggested hooks and ask the user which one to create. If they describe a custom purpose, proceed with that.
Step 2 โ Configure the hook
Ask only what you don't already know from the user's description:
-
Event type โ when should it fire?
Tool execution:
PreToolUse: before a tool call โ can block (exit 2), modify input (updatedInput), or inject context. Best for gates, security checks, and permission decisions.
PostToolUse: after a tool call succeeds โ provides feedback/fixes via additionalContext. Best for quality enforcement (linting, formatting, type-checking).
PostToolUseFailure: after a tool call fails โ react to errors.
PermissionRequest: when a permission prompt appears โ can auto-decide via permissionDecision: allow|deny|ask or add rules via updatedPermissions.
User interaction:
UserPromptSubmit: before Claude processes a message. Best for context injection.
Notification: on permission prompts, idle prompts, or auth events (matcher: permission_prompt, idle_prompt, auth_success).
Session lifecycle:
SessionStart: on startup, resume, clear, or compact (use matcher to filter).
SessionEnd: on clear, resume, logout, or exit.
InstructionsLoaded: when CLAUDE.md and rules are loaded (matcher: session_start, nested_traversal, path_glob_match).
Stop: when Claude stops generating a response.
StopFailure: on rate limit, auth failure, or billing error.
Context management:
PreCompact: before context compaction (matcher: manual or auto).
PostCompact: after context compaction.
Agent operations:
SubagentStart: when a subagent launches (matcher: agent type name).
SubagentStop: when a subagent finishes.
TeammateIdle: when an agent team member is idle.
TaskCreated: when a task is being created (exit 2 to prevent creation and send feedback).
TaskCompleted: when a background task finishes.
File system:
CwdChanged: when the working directory changes.
FileChanged: when a file is modified on disk.
Version control:
WorktreeCreate: when a git worktree is created for isolated work.
WorktreeRemove: when a git worktree is cleaned up.
MCP integration:
Elicitation: when an MCP server requests user input (matcher: server name).
ElicitationResult: after user responds to an MCP elicitation.
Configuration:
ConfigChange: when settings change (matcher: user_settings, project_settings, policy_settings).
Setup:
Setup: triggered via --init, --init-only, or --maintenance CLI flags.
-
Hook type โ how should it run?
command: run a local script (default). Best for most hooks. JSON on stdin, JSON on stdout, exit codes (0=success, 2=block).
http: POST JSON to a URL. Best for external integrations (Slack, logging, CI triggers). Supports headers with env var interpolation via allowedEnvVars. Response body treated same as command stdout.
prompt: single-turn LLM evaluation โ no script needed. Specify the prompt text and optionally a model. Best for smart gates that need reasoning (e.g. "does this edit break the API contract?").
agent: spawn a subagent for verification. Best for complex checks that need multi-step investigation.
-
Tool matcher โ which tool(s) should trigger it? (e.g. Write, Edit, Bash, * for all)
-
Scope โ where should the hook live?
project: .claude/hooks/ โ committed to git, shared with team
global: ~/.claude/hooks/ โ applies to all your projects
project-local: registered in .claude/settings.local.json โ your personal preference, gitignored
-
Claude integration โ should Claude see the hook's output and act on it?
- Yes โ use
additionalContext in the response body for errors; Claude will try to fix them
- No โ use
suppressOutput: true for silent operation
-
File scope โ what file extensions should trigger it? (e.g. .ts,.tsx, * for all)
Step 3 โ Create the hook script
If the user chose an HTTP, prompt, or agent hook: skip this step โ no script file is needed. Go directly to Step 4.
Create the hook script at the appropriate location:
- Project scope:
.claude/hooks/<hook-name>.js (or .sh for simple bash hooks)
- Global scope:
~/.claude/hooks/<hook-name>.js
- Create the directory if it does not exist
Critical implementation rules:
- Input: always read JSON from
stdin โ never argv. Pattern: const input = JSON.parse(await readStdin())
- Success response:
{ continue: true, suppressOutput: true } โ keeps context clean
- Error response:
{ continue: true, additionalContext: "error details here" } โ triggers Claude auto-fix
- Block operation (PreToolUse only): exit with code
2 โ halts the tool call
- Focus on changed files: extract the file path from the stdin JSON and only process that file โ don't scan the whole codebase
- Use
$CLAUDE_PROJECT_DIR for absolute paths โ never relative paths
- Hooks run in parallel โ design each hook to be independent (no shared state, no order assumptions)
- Include a shebang and make the file executable:
chmod +x <script>
Add a comment block at the top of the script explaining what it does, what event it handles, and what tool it matches.
Step 4 โ Register the hook
Update the appropriate settings.json:
- Project scope:
.claude/settings.json
- Global scope:
~/.claude/settings.json
- Project-local:
.claude/settings.local.json
Add an entry under the correct event key.
Command hook (runs a local script):
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/<hook-name>.js"
}
]
}
]
}
}
HTTP hook (POSTs JSON to a URL):
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "http",
"url": "https://example.com/hooks/on-edit"
}
]
}
]
}
}
Prompt hook (LLM evaluation โ no script):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "prompt",
"prompt": "Review the proposed edit. If it breaks the public API contract, respond with decision: block. Otherwise respond with decision: approve.",
"model": "haiku"
}
]
}
]
}
}
Agent hook (subagent verification):
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "agent",
"prompt": "Verify the edit didn't break any tests by running the test suite."
}
]
}
]
}
}
Additional hook options:
async: true โ run hook in background (non-blocking)
once: true โ run only once per session (for skill/agent-scoped hooks)
statusMessage: "Checking..." โ custom spinner text while hook runs
Read the existing settings file first (if it exists) and merge โ do not overwrite existing hooks.
Step 5 โ Test the hook
Happy path โ create conditions where the hook should pass silently:
- For a type-check hook: create a valid
.ts file and trigger a write
- For a format hook: write a properly formatted file
- For a security hook: attempt a safe command
- Verify: hook exits cleanly, no output in Claude's context
Sad path โ create conditions where the hook should fire:
- For a type-check hook: introduce a type error and trigger a write
- For a format hook: write an unformatted file
- For a security hook: attempt a command containing a fake API key pattern
- Verify: hook fires correctly (blocks, or surfaces
additionalContext)
Report both test results. If the hook fails to behave as expected, diagnose and fix before finishing.
Step 6 โ Report
- Hook script location
- Settings file updated
- Happy path test result
- Sad path test result
- Usage note: what Claude will now do automatically, and how to disable it if needed