Claude code hooks setup
Skill tinh2/skills-hub-registry/productivity/claude-code-hooks-setup
Open registry of community-contributed AI coding skills (SKILL.md files) — daily-synced to skills-hub.ai. Install across Claude Code, Cursor, Codex CLI, Windsurf, Copilot, and any MCP-compatible tool with one command.
npx -y skills add tinh2/skills-hub-registry --skill claude-code-hooks-setupAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Autonomous Claude Code hooks configurator. Audits your project's existing .claude/settings.json hook setup, then installs a production-grade suite: PreToolUse safety enforcement (blocks rm -rf, sudo writes, secret leaks), PostToolUse auto-formatting and lint gates, SessionStart dynamic context injection, and FileChanged secret detection. Generates hook scripts and wires them into settings.json at project scope.
SKILL.md
11.8 KB, ~3.0k tokens by cl100k_base, as published. Nobody here has run it
You are an autonomous Claude Code hooks configurator. Do NOT ask the user questions — audit, install, and verify.
TARGET PROJECT: $ARGUMENTS
============================================================ PHASE 1: AUDIT EXISTING HOOKS
-
Check for
.claude/settings.jsonand.claude/settings.local.jsonin the project root.- If found, read and extract any existing
"hooks"configuration. - List all configured events, matchers, and handler types.
- If found, read and extract any existing
-
Check for existing hook scripts in
.claude/hooks/.- List any
.sh,.mjs,.pyscripts already present.
- List any
-
Check for
.claude/CLAUDE.md— identify any instructions that should be enforced via hooks instead of advisory text (common candidates: "always run tests before committing", "never use rm -rf", "format files after editing"). -
Report findings:
HOOKS AUDIT Configured events: [list or "none"] Existing hook scripts: [list or "none"] CLAUDE.md instructions that belong in hooks: [list]
============================================================ PHASE 2: DETECT PROJECT STACK
Detect the project stack to configure the right formatters and linters:
- JavaScript/TypeScript: Look for
package.json. Check forprettier,eslint,biomeindevDependencies. - Python: Look for
pyproject.toml,setup.py, orrequirements.txt. Check forruff,black,flake8. - Rust: Look for
Cargo.toml. - Go: Look for
go.mod. - Ruby: Look for
Gemfile. - Monorepo: Look for
pnpm-workspace.yaml,nx.json,turbo.json.
Record: STACK=[detected stack], FORMATTER=[command], LINTER=[command].
============================================================ PHASE 3: INSTALL HOOK SCRIPTS
Create .claude/hooks/ directory if it does not exist. Install the following scripts:
3a. PreToolUse safety check (check-command.sh)
#!/usr/bin/env bash
# claude-code-hooks-setup: PreToolUse safety enforcement
# Blocks destructive Bash commands and writes outside the project root.
set -euo pipefail
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
# Block destructive shell patterns
if [[ "$TOOL_NAME" == "Bash" ]]; then
if echo "$COMMAND" | grep -qE '^\s*(rm\s+-[rRf]*f|sudo\s+rm|dd\s+if=|mkfs|shred)'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by project policy. Use targeted file deletions instead."
}
}'
exit 0
fi
fi
# Block writes outside project root
PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
if [[ -n "$FILE_PATH" && "$FILE_PATH" != "$PROJECT_ROOT"* ]]; then
jq -n --arg path "$FILE_PATH" --arg root "$PROJECT_ROOT" '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "ask",
permissionDecisionReason: ("File path " + $path + " is outside project root " + $root + " — confirm this is intentional.")
}
}'
exit 0
fi
exit 0
Make executable: chmod +x .claude/hooks/check-command.sh
3b. PostToolUse formatter (post-write.sh)
Adapt based on detected stack:
TypeScript/JavaScript (Prettier + ESLint):
#!/usr/bin/env bash
# claude-code-hooks-setup: PostToolUse auto-format
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
if [[ -z "$FILE_PATH" ]]; then exit 0; fi
# Only format source files
if [[ "$FILE_PATH" =~ \.(ts|tsx|js|jsx|mjs|cjs|css|json|md)$ ]]; then
if command -v prettier &>/dev/null; then
npx prettier --write "$FILE_PATH" 2>/dev/null || true
fi
LINT_STATUS="skipped"
if [[ "$FILE_PATH" =~ \.(ts|tsx|js|jsx)$ ]]; then
if npx eslint "$FILE_PATH" --max-warnings 0 2>/dev/null; then
LINT_STATUS="passed"
else
LINT_STATUS="failed — run: npx eslint $FILE_PATH"
fi
fi
jq -n --arg status "$LINT_STATUS" '{
hookSpecificOutput: {
hookEventName: "PostToolUse",
additionalContext: ("Lint: " + $status)
}
}'
fi
exit 0
Python (ruff):
#!/usr/bin/env bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
if [[ "$FILE_PATH" =~ \.py$ ]]; then
ruff format "$FILE_PATH" 2>/dev/null || true
ruff check "$FILE_PATH" --fix 2>/dev/null || true
fi
exit 0
Rust:
#!/usr/bin/env bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
if [[ "$FILE_PATH" =~ \.rs$ ]]; then
rustfmt "$FILE_PATH" 2>/dev/null || true
fi
exit 0
Go:
#!/usr/bin/env bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
if [[ "$FILE_PATH" =~ \.go$ ]]; then
gofmt -w "$FILE_PATH" 2>/dev/null || true
fi
exit 0
Make executable: chmod +x .claude/hooks/post-write.sh
3c. SessionStart context injector (session-start.sh)
#!/usr/bin/env bash
# claude-code-hooks-setup: SessionStart — inject branch, env, and skill context
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
DIRTY=$(git status --short 2>/dev/null | wc -l | tr -d ' ')
ENV_STATUS="missing — copy .env.example before running"
[[ -f ".env" ]] && ENV_STATUS="found"
SKILL_LIST=$(ls .claude/skills/ 2>/dev/null | tr '\n' ', ' | sed 's/, $//')
[[ -z "$SKILL_LIST" ]] && SKILL_LIST="none"
NODE_VER=$(node --version 2>/dev/null || echo "not installed")
PKG_MANAGER=""
[[ -f "pnpm-lock.yaml" ]] && PKG_MANAGER="pnpm"
[[ -f "yarn.lock" ]] && PKG_MANAGER="yarn"
[[ -f "package-lock.json" ]] && PKG_MANAGER="npm"
jq -n \
--arg branch "$BRANCH" \
--arg dirty "$DIRTY" \
--arg env "$ENV_STATUS" \
--arg skills "$SKILL_LIST" \
--arg node "$NODE_VER" \
--arg pkg "$PKG_MANAGER" \
'{
hookSpecificOutput: {
hookEventName: "SessionStart",
reloadSkills: true,
sessionTitle: ("branch: " + $branch),
additionalContext: (
"Git branch: " + $branch + " (" + $dirty + " uncommitted files)\n" +
".env: " + $env + "\n" +
"Node: " + $node + " | Package manager: " + $pkg + "\n" +
"Installed skills: " + $skills + "\n" +
"Run /hooks to see all active lifecycle hooks."
)
}
}'
Make executable: chmod +x .claude/hooks/session-start.sh
3d. FileChanged secret detector (file-changed.sh)
#!/usr/bin/env bash
# claude-code-hooks-setup: FileChanged — detect new secrets added to .env files
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.file_path // empty')
if [[ "$FILE_PATH" =~ \.env ]]; then
# Check for patterns that look like new secrets
if grep -qE '(SECRET|PASSWORD|API_KEY|TOKEN|PRIVATE_KEY)\s*=\s*\S+' "$FILE_PATH" 2>/dev/null; then
jq -n --arg path "$FILE_PATH" '{
systemMessage: ("Warning: secret-like values detected in " + $path + ". Verify this file is in .gitignore before committing."),
hookSpecificOutput: {
hookEventName: "FileChanged",
additionalContext: "Secret detection: env file changed — check .gitignore coverage."
}
}'
fi
fi
exit 0
Make executable: chmod +x .claude/hooks/file-changed.sh
============================================================ PHASE 4: WRITE settings.json
Read the existing .claude/settings.json (or create it if absent). Merge in the hooks configuration — preserve any existing settings, do NOT overwrite them.
Target hooks block to merge:
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/session-start.sh",
"timeout": 15,
"statusMessage": "Loading session context..."
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash|Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-command.sh",
"timeout": 10,
"statusMessage": "Checking command safety..."
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/post-write.sh",
"timeout": 30,
"statusMessage": "Formatting and linting..."
}
]
}
],
"FileChanged": [
{
"matcher": "\\.env|\\.env\\..*",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/file-changed.sh",
"async": true
}
]
}
]
}
}
Write the merged result back to .claude/settings.json.
============================================================ PHASE 5: VALIDATE
-
Verify all four hook scripts exist and are executable:
ls -la .claude/hooks/ -
Verify
.claude/settings.jsoncontains a"hooks"key with all four events. -
Dry-run the session-start script to confirm it produces valid JSON:
echo '{}' | .claude/hooks/session-start.sh | jq . -
Dry-run the safety check with a safe command to confirm exit 0:
echo '{"tool_name":"Bash","tool_input":{"command":"echo hello"}}' | .claude/hooks/check-command.sh | jq . -
Dry-run the safety check with a destructive command to confirm deny:
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /tmp/test"}}' | .claude/hooks/check-command.sh | jq .Expected:
permissionDecision: "deny"
============================================================ PHASE 6: REPORT
Output a summary:
HOOKS SETUP COMPLETE
Scripts installed:
✓ .claude/hooks/check-command.sh (PreToolUse safety)
✓ .claude/hooks/post-write.sh (PostToolUse formatter)
✓ .claude/hooks/session-start.sh (SessionStart context)
✓ .claude/hooks/file-changed.sh (FileChanged secrets)
settings.json events configured:
✓ SessionStart → session-start.sh
✓ PreToolUse → check-command.sh (matcher: Bash|Write|Edit)
✓ PostToolUse → post-write.sh (matcher: Write|Edit)
✓ FileChanged → file-changed.sh (matcher: .env files)
Stack detected: [STACK]
Formatter: [FORMATTER]
Linter: [LINTER]
Validation: [PASS/FAIL with details]
Next steps:
- Run /hooks inside a Claude Code session to verify all hooks appear
- Commit .claude/settings.json and .claude/hooks/ to share with your team
- Add .claude/settings.local.json to .gitignore for user-specific overrides
============================================================ STRICT RULES
- Never overwrite existing settings.json fields other than "hooks".
- Never install hooks that require network access without user acknowledgment.
- If jq is not installed, abort and instruct the user to install it first.
- Always make hook scripts executable (chmod +x) after writing them.
- Hook scripts must handle empty tool_input gracefully (exit 0 if no relevant input).
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.