CloudCodeTree LogoCloudCodeTree
AI NewsTutorialsAbout
CloudCodeTree Logo
CloudCodeTree
  • AI News
  • Tutorials
  • About
← Back to AI News
Catch Bugs Before CI Does: Wire `claude -p` Into a Git Pre-Commit Hook in 15 Lines

Catch Bugs Before CI Does: Wire `claude -p` Into a Git Pre-Commit Hook in 15 Lines

Chris Harper

3 min read

Jul 21, 2026 · 12:03 UTC

AI
Workflow
Claude Code
Best Practices

TL;DR: Pipe git diff --staged into claude -p from a pre-commit hook and get a focused bug/security review on every commit — 15 lines of bash, zero CI cost.

CI catches what lands on main. A pre-commit hook catches what's about to leave your machine. Wiring Claude's headless mode into that hook gives you a fast, opinionated reviewer on every staged diff before you type git push.

The hook

Save this as .git/hooks/pre-commit and make it executable (chmod +x .git/hooks/pre-commit):

#!/usr/bin/env bash
set -euo pipefail

DIFF=$(git diff --staged)
[[ -z "$DIFF" ]] && exit 0  # nothing staged, skip

REVIEW=$(echo "$DIFF" | claude -p "
You are a senior code reviewer. Review this git diff for:
1. Logic bugs or off-by-one errors
2. Security issues (injection, unvalidated input, exposed secrets)
3. Missing null/error handling on code paths that exist in this diff

Respond ONLY with a JSON object: {\"severity\": \"none|low|medium|high\", \"findings\": [\"<issue1>\", \"<issue2>\"]}
Severity 'high' = commit should be blocked. 'none' = looks clean.
" 2>/dev/null)

SEVERITY=$(echo "$REVIEW" | python3 -c "import sys,json; print(json.load(sys.stdin)['severity'])" 2>/dev/null || echo "none")
FINDINGS=$(echo "$REVIEW" | python3 -c "import sys,json; [print('  •', f) for f in json.load(sys.stdin)['findings']]" 2>/dev/null || true)

if [[ "$SEVERITY" == "high" ]]; then
  echo "🚫 Claude pre-commit review: HIGH severity — commit blocked"
  echo "$FINDINGS"
  exit 1
elif [[ "$SEVERITY" != "none" ]]; then
  echo "⚠️  Claude pre-commit review ($SEVERITY):"
  echo "$FINDINGS"
fi

How it works

  • -p (print mode): Claude runs non-interactively, reads from stdin, writes to stdout, exits — perfect for shell pipelines.
  • Structured JSON output: The prompt constrains Claude to a JSON schema so python3 -c can parse severity and findings without fragile regex.
  • exit 1 on high severity: Git aborts the commit. Fix and re-stage to proceed.
  • Non-zero but non-high findings: Printed as warnings, commit proceeds — useful for "I saw this but it's intentional" situations.

Bypass when intentional

git commit --no-verify  # bypass all hooks for this commit
SKIP_CLAUDE_REVIEW=1 git commit  # add a [[ -n "$SKIP_CLAUDE_REVIEW" ]] && exit 0 guard

Shareable via git template

To apply this to every new repo on your machine:

mkdir -p ~/.git-template/hooks
cp .git/hooks/pre-commit ~/.git-template/hooks/
git config --global init.templateDir ~/.git-template
# future: git init  →  hook installed automatically

The hook is fastest on small diffs. For large staged sets, add head -c 8000 after git diff --staged to cap the input to Claude.

Sources: Claude Code headless mode (-p flag) — code.claude.com · Git hooks documentation — git-scm.com · Claude Code common workflows — code.claude.com