Last active
February 16, 2026 19:12
-
-
Save natew/1ce159ad11509bdb6688acbcbd9eeb33 to your computer and use it in GitHub Desktop.
Claude Code hooks - block destructive git commands
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # unified bash command interceptor | |
| set -o pipefail | |
| # silently exit on any error - don't block claude | |
| trap 'exit 0' ERR | |
| INPUT=$(cat 2>/dev/null) || exit 0 | |
| COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null) || exit 0 | |
| # no command = nothing to check | |
| [[ -z "$COMMAND" ]] && exit 0 | |
| deny() { | |
| jq -n --arg reason "$1" '{ | |
| "hookSpecificOutput": { | |
| "hookEventName": "PreToolUse", | |
| "permissionDecision": "deny", | |
| "permissionDecisionReason": $reason | |
| } | |
| }' | |
| exit 0 | |
| } | |
| warn() { | |
| jq -n --arg reason "$1" '{ | |
| "hookSpecificOutput": { | |
| "hookEventName": "PreToolUse", | |
| "permissionDecision": "ask", | |
| "permissionDecisionReason": $reason | |
| } | |
| }' | |
| exit 0 | |
| } | |
| # block pkill -f (can kill unrelated processes) | |
| if echo "$COMMAND" | grep -qE 'pkill\s+(-\w+\s+)*-f|pkill\s+-f'; then | |
| deny "pkill -f is blocked - can kill unrelated processes. Use: kill-port <port>" | |
| fi | |
| # block release commands that skip tests | |
| if echo "$COMMAND" | grep -qE 'bun release|npm publish'; then | |
| if ! echo "$COMMAND" | grep -q '\-\-nate-told-me-i-could'; then | |
| if echo "$COMMAND" | grep -qiE 'SKIP.*TEST|--skip-test|--no-test'; then | |
| deny "NEVER skip tests during release. Fix them. (bypass: --nate-told-me-i-could)" | |
| fi | |
| fi | |
| fi | |
| # block git push to main/master without explicit ask | |
| if echo "$COMMAND" | grep -qE 'git\s+push'; then | |
| CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || true | |
| if [[ "$CURRENT_BRANCH" == "main" || "$CURRENT_BRANCH" == "master" ]]; then | |
| deny "STOP! Pushing to main. Were you asked to push? /alert and confirm first." | |
| fi | |
| fi | |
| # block git reset --hard (destroys uncommitted work) | |
| if echo "$COMMAND" | grep -qE 'git\s+reset\s+.*--hard|git\s+reset\s+--hard'; then | |
| deny "BLOCKED: git reset --hard destroys uncommitted work. Use --soft or stash instead." | |
| fi | |
| # block git checkout . and git restore . (destroys uncommitted work) | |
| if echo "$COMMAND" | grep -qE 'git\s+(checkout|restore)\s+\.'; then | |
| deny "BLOCKED: This destroys uncommitted work. Be specific about files or stash first." | |
| fi | |
| # block git clean -f (deletes untracked files) | |
| if echo "$COMMAND" | grep -qE 'git\s+clean\s+.*-f'; then | |
| deny "BLOCKED: git clean -f deletes untracked files permanently." | |
| fi | |
| # warn on git stash (might stash other agent's work) | |
| # add "# safe" to command to bypass after checking | |
| if echo "$COMMAND" | grep -qE 'git\s+stash(\s|$)'; then | |
| if ! echo "$COMMAND" | grep -q '# safe'; then | |
| deny "STOP: Before stashing - run git status. If there's work that isn't yours, /alert and ask. Add '# safe' to bypass." | |
| fi | |
| fi | |
| # warn on git reset --soft (safer but still changes state) | |
| # add "# safe" to command to bypass after checking | |
| if echo "$COMMAND" | grep -qE 'git\s+reset\s+.*--soft|git\s+reset\s+--soft'; then | |
| if ! echo "$COMMAND" | grep -q '# safe'; then | |
| deny "STOP: Before reset --soft - run git status/log. Make sure this is intentional. Add '# safe' to bypass." | |
| fi | |
| fi | |
| exit 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "$schema": "https://json.schemastore.org/claude-code-settings.json", | |
| "hooks": { | |
| "PreToolUse": [ | |
| { | |
| "matcher": "Bash", | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": "~/.claude/hooks/intercept-bash.sh" | |
| } | |
| ] | |
| } | |
| ] | |
| }, | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment