On macOS /dev/tty always exists, so '[ -e /dev/tty ]' passed even in agent
harnesses/CI with no controlling terminal — the subsequent read then died
with ENXIO ('Device not configured') and blocked the commit instead of taking
the hook's own non-interactive branch. Probe with '( : < /dev/tty )' — the
exact operation that fails. Both prompt sites fixed; the deliberate asymmetry
preserved (debug keywords warn-and-proceed; secrets fail closed). Verified
both paths from a non-TTY context. Surfaced via the be a11y-gate commit
bypass logged in today's ledger.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
78 lines
2.9 KiB
Bash
Executable File
78 lines
2.9 KiB
Bash
Executable File
#!/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
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
echo "Running pre-commit checks..."
|
|
|
|
# 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
|
|
# Probe by OPENING /dev/tty, not testing existence — on macOS /dev/tty always
|
|
# exists ([ -e ] passes) but open() fails with ENXIO when there is no controlling
|
|
# terminal (agent harnesses, CI), which used to kill the read and block the commit.
|
|
if ( : < /dev/tty ) 2>/dev/null; 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) — operates on file size, not diff content
|
|
for file in $(git diff --cached --name-only); do
|
|
if [ -f "$file" ]; then
|
|
size=$(wc -c < "$file")
|
|
if [ $size -gt 5242880 ]; then
|
|
echo -e "${RED}Error: $file is larger than 5MB${NC}"
|
|
echo "Consider using Git LFS for large files"
|
|
exit 1
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# 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
|
|
# Same open()-probe as above. Note the deliberate asymmetry: secrets BLOCK when
|
|
# non-interactive (fail closed); debug keywords proceed with a warning.
|
|
if ( : < /dev/tty ) 2>/dev/null; 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}"
|