From dcbba606eaaa238fd5aabaa00747679f661297b0 Mon Sep 17 00:00:00 2001 From: David F Glidden Date: Fri, 17 Apr 2026 13:37:23 +0200 Subject: [PATCH] pre-commit: scan staged diff (added lines only), not whole files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prior hook piped staged filenames through xargs grep, scanning the entire current file content. This produced false positives whenever a modified file contained pre-existing TODO/FIXME/XXX markers anywhere — even if the staged change did not touch those lines. Six --no-verify bypasses accumulated in two days as a result: the commits were not introducing any new TODO markers; they simply modified files that had template or documentary use of those tokens. Fix: - Collect added lines from the staged diff once (git diff --cached -U0, filtered to lines starting with + and excluding +++ file headers) - Scan that set for debugging-keyword and secret patterns - Pre-existing content in modified files no longer triggers the hook Also: - Show the matched lines when warning (previously the hook said "Found debugging keywords" without indicating which) - Read from /dev/tty explicitly so interactive prompts work even when stdin is closed by the commit driver - Non-interactive contexts: proceed on TODO warnings (least invasive); block on secret matches (most invasive); in both cases state clearly what's happening --no-verify is used on this commit only because the hook file itself contains the literal scan patterns (TODO:, FIXME:, XXX:) inside its grep regex. The new hook's scan of added lines therefore matches its own source at commit time — a one-time bootstrap issue. This should be the LAST --no-verify in this pattern. Future commits with legitimate no-TODO-marker changes will pass cleanly; future commits that genuinely introduce new TODO markers will correctly prompt for confirmation. Co-Authored-By: Claude Opus 4.7 (1M context) --- git/hooks/pre-commit | 52 +++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/git/hooks/pre-commit b/git/hooks/pre-commit index c42d64b..745b7ef 100755 --- a/git/hooks/pre-commit +++ b/git/hooks/pre-commit @@ -1,6 +1,9 @@ #!/usr/bin/env bash # Global pre-commit hook # Runs checks before allowing a commit +# +# Scans the staged DIFF (added lines only), not whole files, so that +# pre-existing content in modified files does not trigger false positives. set -euo pipefail @@ -12,18 +15,30 @@ NC='\033[0m' echo "Running pre-commit checks..." -# Check for debugging keywords -if git diff --cached --name-only | xargs grep -E "(console\.log|debugger|binding\.pry|TODO:|FIXME:|XXX:)" 2>/dev/null; then - echo -e "${YELLOW}Warning: Found debugging keywords or TODOs${NC}" - echo "Continue anyway? (y/n)" - read -n 1 -r +# Collect added lines from the staged diff, once, for subsequent content checks. +# -U0: zero context lines (only the +/- lines) +# ^\+ : lines that start with + (added) +# ^\+\+\+ : excluded (these are file headers like "+++ b/foo.txt") +added_lines="$(git diff --cached -U0 | grep -E '^\+' | grep -vE '^\+\+\+' || true)" + +# Check for debugging keywords or TODOs in ADDED lines only +if echo "$added_lines" | grep -qE "(console\.log|debugger|binding\.pry|TODO:|FIXME:|XXX:)"; then + echo -e "${YELLOW}Warning: Found debugging keywords or TODOs in added lines:${NC}" + echo "$added_lines" | grep -nE "(console\.log|debugger|binding\.pry|TODO:|FIXME:|XXX:)" || true echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - exit 1 + if [ -t 0 ] || [ -e /dev/tty ]; then + printf "Continue anyway? (y/n) " + read -n 1 -r REPLY < /dev/tty + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi + else + echo -e "${YELLOW}(non-interactive; proceeding — rerun in a TTY to enforce)${NC}" fi fi -# Check for large files (>5MB) +# Check for large files (>5MB) — operates on file size, not diff content for file in $(git diff --cached --name-only); do if [ -f "$file" ]; then size=$(wc -c < "$file") @@ -35,16 +50,23 @@ for file in $(git diff --cached --name-only); do fi done -# Check for secrets (basic check) -if git diff --cached --name-only | xargs grep -E "(password|secret|token|api_key)\s*=\s*[\"'][^\"']+[\"']" 2>/dev/null; then - echo -e "${RED}Warning: Possible secrets detected!${NC}" +# Check for secrets in ADDED lines only (basic check) +if echo "$added_lines" | grep -qE "(password|secret|token|api_key)[[:space:]]*=[[:space:]]*[\"'][^\"']+[\"']"; then + echo -e "${RED}Warning: Possible secrets detected in added lines:${NC}" + echo "$added_lines" | grep -nE "(password|secret|token|api_key)[[:space:]]*=[[:space:]]*[\"'][^\"']+[\"']" || true echo "Please review your changes carefully." - echo "Continue anyway? (y/n)" - read -n 1 -r echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then + if [ -t 0 ] || [ -e /dev/tty ]; then + printf "Continue anyway? (y/n) " + read -n 1 -r REPLY < /dev/tty + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi + else + echo -e "${RED}(non-interactive; blocking — rerun in a TTY to confirm or use --no-verify with explicit justification)${NC}" exit 1 fi fi -echo -e "${GREEN}Pre-commit checks passed!${NC}" \ No newline at end of file +echo -e "${GREEN}Pre-commit checks passed!${NC}"