claude-code-hooks

安装量: 51
排名: #14447

安装

npx skills add https://github.com/vasilyu1983/ai-agents-public --skill claude-code-hooks

Claude Code Hooks — Meta Reference This skill provides the definitive reference for creating Claude Code hooks. Use this when building automation that triggers on Claude Code events. When to Use This Skill Building event-driven automation for Claude Code Creating PreToolUse guards to block dangerous commands Implementing PostToolUse formatters, linters, or auditors Adding Stop hooks for testing or notifications Setting up SessionStart/SessionEnd for environment management Integrating Claude Code with CI/CD pipelines (headless mode) Quick Reference Event Trigger Use Case SessionStart Session begins/resumes Initialize environment UserPromptSubmit User submits prompt Preprocess/validate input PreToolUse Before tool execution Validate, block dangerous commands PermissionRequest Permission dialog shown Auto-allow/deny permissions PostToolUse After tool succeeds Format, audit, notify PostToolUseFailure After tool fails Capture failures, add guidance SubagentStart Subagent spawns Inspect subagent metadata Stop When Claude finishes Run tests, summarize SubagentStop Subagent finishes Verify subagent completion Notification On notifications Alert integrations PreCompact Before context compaction Preserve critical context Setup --init / --maintenance Initialize repo/env SessionEnd Session ends Cleanup, save state Hook Structure .claude/hooks/ ├── pre-tool-validate.sh ├── post-tool-format.sh ├── post-tool-audit.sh ├── stop-run-tests.sh └── session-start-init.sh Configuration settings.json { "hooks" : { "PostToolUse" : [ { "matcher" : "Edit|Write" , "hooks" : [ { "type" : "command" , "command" : "$CLAUDE_PROJECT_DIR/.claude/hooks/post-tool-format.sh" } ] } ] , "PreToolUse" : [ { "matcher" : "Bash" , "hooks" : [ { "type" : "command" , "command" : "$CLAUDE_PROJECT_DIR/.claude/hooks/pre-tool-validate.sh" } ] } ] , "Stop" : [ { "hooks" : [ { "type" : "command" , "command" : "$CLAUDE_PROJECT_DIR/.claude/hooks/stop-run-tests.sh" } ] } ] } } Execution Model (Jan 2026) Hooks receive a JSON payload via stdin (treat it as untrusted input) and run with your user permissions (outside the Bash tool sandbox). Default timeout is 60s per hook command; all matching hooks run in parallel; identical commands are deduplicated. Hook Input (stdin) { "hook_event_name" : "PreToolUse" , "tool_name" : "Bash" , "tool_input" : { "command" : "ls -la" } } Environment Variables (shell) Variable Description CLAUDE_PROJECT_DIR Absolute project root where Claude Code started CLAUDE_PLUGIN_ROOT Plugin root (plugin hooks only) CLAUDE_CODE_REMOTE "true" in remote/web environments; empty/local otherwise CLAUDE_ENV_FILE File path to persist export ... lines (available in SessionStart; check docs for Setup support) Exit Codes Code Meaning Notes 0 Success JSON written to stdout is parsed for structured control 2 Blocking error stderr becomes the message; JSON in stdout is ignored Other Non-blocking error Execution continues; stderr is visible in verbose mode Stdout injection note: for UserPromptSubmit , SessionStart , and Setup , non-JSON stdout (exit 0) is injected into Claude’s context; most other events show stdout only in verbose mode. Decision Control + Input Modification (v2.0.10+) PreToolUse hooks can allow/deny/ask and optionally modify the tool input via updatedInput . Hook Output Schema { "hookSpecificOutput" : { "hookEventName" : "PreToolUse" , "permissionDecision" : "allow" , "permissionDecisionReason" : "Reason shown to user (and to Claude on deny)" , "updatedInput" : { "command" : "echo 'modified'" } , "additionalContext" : "Extra context added before tool runs" } } Note: older decision / reason fields are deprecated; prefer the hookSpecificOutput.* fields. Example: Redirect Sensitive File Edits

!/bin/bash

set -euo pipefail INPUT = " $( cat ) " FILE_PATH = " $( echo " $INPUT " | jq -r '.tool_input.file_path // empty' ) "

Redirect package-lock.json edits to /dev/null

if [ [ " $FILE_PATH " == * "package-lock.json" ] ] ; then UPDATED_INPUT = " $( echo " $INPUT " | jq -c '.tool_input | .file_path = "/dev/null"' ) " jq -cn --argjson updatedInput " $UPDATED_INPUT " '{ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "allow", permissionDecisionReason: "Redirected write to /dev/null", updatedInput: $updatedInput } }' exit 0 fi echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}' Example: Strip Sensitive Files from Git Add

!/bin/bash

set -euo pipefail INPUT = " $( cat ) " TOOL_NAME = " $( echo " $INPUT " | jq -r '.tool_name' ) " CMD = " $( echo " $INPUT " | jq -r '.tool_input.command // empty' ) " if [ [ " $TOOL_NAME " == "Bash" && " $CMD " =~ ^git [ [ :space: ] ] +add ] ] ; then

Remove .env files from staging

SAFE_CMD

" $( echo " $CMD " | sed 's/.env[^ ]*//g' ) " if [ [ " $SAFE_CMD " != " $CMD " ] ] ; then echo '{}' | jq -cn --arg cmd " $SAFE_CMD " '{ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "allow", permissionDecisionReason: "Removed .env from git add", updatedInput: { command: $cmd } } }' exit 0 fi fi echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}' Prompt-Based Hooks For complex decisions, use LLM-evaluated hooks ( type: "prompt" ) instead of bash scripts. They are most useful for Stop and SubagentStop decisions. Configuration { "hooks" : { "Stop" : [ { "hooks" : [ { "type" : "prompt" , "prompt" : "Evaluate whether Claude should stop. Context JSON: $ARGUMENTS. Return {\"ok\": true} if all tasks are complete, otherwise {\"ok\": false, \"reason\": \"what remains\"}." , "timeout" : 30 } ] } ] } } Response Schema Allow: {"ok": true} Block: {"ok": false, "reason": "Explanation shown to Claude"} Combining Command and Prompt Hooks Use command hooks for fast, deterministic checks. Use prompt hooks for nuanced decisions: { "Stop" : [ { "hooks" : [ { "type" : "command" , "command" : ".claude/hooks/quick-check.sh" } , { "type" : "prompt" , "prompt" : "Verify code quality meets standards" } ] } ] } Hook Templates Pre-Tool Validation

!/bin/bash

set -euo pipefail INPUT = " $( cat ) " TOOL_NAME = " $( echo " $INPUT " | jq -r '.tool_name' ) " CMD = " $( echo " $INPUT " | jq -r '.tool_input.command // empty' ) " if [ [ " $TOOL_NAME " == "Bash" ] ] ; then

Block rm -rf /

if echo " $CMD " | grep -qE 'rm\s+-rf\s+/' ; then echo '{}' | jq -cn '{ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: "Dangerous rm command detected" } }' exit 0 fi

Block force push to main

if echo " $CMD " | grep -qE 'git\s+push.--force.(main|master)' ; then echo '{}' | jq -cn '{ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: "Force push to main/master not allowed" } }' exit 0 fi

Soft-warning: possible credential exposure

if echo " $CMD " | grep -qE '(password|secret|api_key)\s*=' ; then echo '{}' | jq -cn '{ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "ask", permissionDecisionReason: "Possible credential exposure in command", additionalContext: "Command may include a secret. Confirm intent and avoid committing secrets." } }' exit 0 fi fi exit 0 Post-Tool Formatting

!/bin/bash

set -euo pipefail INPUT = " $( cat ) " TOOL_NAME = " $( echo " $INPUT " | jq -r '.tool_name' ) " FILE_PATH = " $( echo " $INPUT " | jq -r '.tool_input.file_path // empty' ) " if [ [ " $TOOL_NAME " =~ ^ ( Edit | Write ) $ && -n " $FILE_PATH " && -f " $FILE_PATH " ] ] ; then case " $FILE_PATH " in .js | .ts | .jsx | .tsx | .json | .md ) npx prettier --write " $FILE_PATH " 2

/dev/null || true ; ; *.py ) ruff format " $FILE_PATH " 2

/dev/null || true ; ; *.go ) gofmt -w " $FILE_PATH " 2

/dev/null || true ; ; *.rs ) rustfmt " $FILE_PATH " 2

/dev/null || true ; ; esac fi exit 0 Post-Tool Security Audit

!/bin/bash

set -euo pipefail INPUT = " $( cat ) " TOOL_NAME = " $( echo " $INPUT " | jq -r '.tool_name' ) " FILE_PATH = " $( echo " $INPUT " | jq -r '.tool_input.file_path // empty' ) " if [ [ " $TOOL_NAME " =~ ^ ( Edit | Write ) $ && -n " $FILE_PATH " && -f " $FILE_PATH " ] ] ; then

Check for hardcoded secrets

if grep -qE '(password|secret|api_key|token)\s[:=]\s["\x27][^"\x27]+["\x27]' " $FILE_PATH " ; then echo "WARNING: Possible hardcoded secret in $FILE_PATH "

&2 fi

Check for console.log in production code

if [ [ " $FILE_PATH " =~ \ . ( ts | js | tsx | jsx ) $ ] ] && grep -q 'console.log' " $FILE_PATH " ; then echo "NOTE: console.log found in $FILE_PATH "

&2 fi fi exit 0 Stop Hook (Run Tests)

!/bin/bash

set -euo pipefail

Run tests after Claude finishes

cd " $CLAUDE_PROJECT_DIR "

Detect test framework

if [ [ -f "package.json" ] ] ; then if grep -q '"vitest"' package.json ; then npm run test 2

&1 | head -50 elif grep -q '"jest"' package.json ; then npm test 2

&1 | head -50 fi elif [ [ -f "pytest.ini" ] ] || [ [ -f "pyproject.toml" ] ] ; then pytest --tb = short 2

&1 | head -50 fi exit 0 Session Start

!/bin/bash

set -euo pipefail cd " $CLAUDE_PROJECT_DIR "

Check git status

echo "=== Git Status ===" git status --short

Check for uncommitted changes

if ! git diff --quiet ; then echo "WARNING: Uncommitted changes detected" fi

Verify dependencies

if [ [ -f "package.json" ] ] ; then if [ [ ! -d "node_modules" ] ] ; then echo "NOTE: node_modules missing, run npm install" fi fi exit 0 Matchers Matchers filter which tool triggers the hook: Exact match: Write matches only the Write tool Regex: Edit|Write or Notebook.* Match all: * (also works with "" or omitted matcher) Security Best Practices HOOK SECURITY CHECKLIST [ ] Validate all inputs with regex [ ] Quote all variables: "$VAR" not $VAR [ ] Use absolute paths [ ] No eval with untrusted input [ ] Set -euo pipefail at top [ ] Keep hooks fast (<1 second) [ ] Log actions for audit [ ] Test manually before deploying Hook Composition Multiple Hooks on Same Event { "PostToolUse" : [ { "matcher" : "Edit|Write" , "hooks" : [ { "type" : "command" , "command" : ".claude/hooks/format.sh" } , { "type" : "command" , "command" : ".claude/hooks/audit.sh" } , { "type" : "command" , "command" : ".claude/hooks/notify.sh" } ] } ] } All matching hooks run in parallel. If you need strict ordering (format → lint → test), make one wrapper script that runs them sequentially. Debugging Hooks

Test a PostToolUse hook manually (stdin JSON)

export CLAUDE_PROJECT_DIR = " $( pwd ) " echo '{"hook_event_name":"PostToolUse","tool_name":"Edit","tool_input":{"file_path":"' " $( pwd ) "'/src/app.ts" } } ' \ | bash .claude/hooks/post-tool-format.sh

Check exit code

echo $? Navigation Resources references/hook-patterns.md — Common patterns references/hook-security.md — Security guide data/sources.json — Documentation links

返回排行榜