Skip to content

Commit ee3dce3

Browse files
fix(hooks): install pre-commit from versioned template to core.hooksPath
Follow-up to e5ba2f1. The active pre-commit hook is resolved via `git config core.hooksPath` (= .git-hooks/), NOT .git/hooks/. The previous e5ba2f1 wired Phase 1b into the wrong live file and into a stale embedded heredoc in install-git-hooks.sh. .git-hooks/ is .gitignore'd ON PURPOSE: the active hook copy stays local and is never committed, keeping hook contents out of this PUBLIC repo (no secret-leak risk). So we do NOT track .git-hooks/; instead the installer regenerates it from the single versioned source of truth. ## Change - scripts/install-git-hooks.sh rewritten (v3.2.0): installs from scripts/pre-commit-template.sh into the directory resolved from core.hooksPath (defaulting to .git-hooks and setting it if unset). Removes the drifted embedded heredoc — one source of truth now (the template, which already carries Phase 1b: PreToolUse permissionDecision allow|deny|ask guard). ## Validation - Installer regenerates .git-hooks/pre-commit identical to the template (Phase 1b present); core.hooksPath preserved as .git-hooks. - Secret-scanned the staged change; .git-hooks confirmed gitignored (uncommittable). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent e5ba2f1 commit ee3dce3

1 file changed

Lines changed: 39 additions & 70 deletions

File tree

scripts/install-git-hooks.sh

Lines changed: 39 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,63 @@
11
#!/bin/bash
22
# install-git-hooks.sh - Install git hooks for multi-agent-ralph-loop
3-
# VERSION: 2.57.3
3+
# VERSION: 3.2.0
4+
#
5+
# Installs the pre-commit hook from the SINGLE versioned source of truth
6+
# (scripts/pre-commit-template.sh) into the active hooks directory resolved from
7+
# `git config core.hooksPath`.
8+
#
9+
# This repo uses core.hooksPath=.git-hooks, which is .gitignored — the active
10+
# hook copy is LOCAL ONLY and is never committed. That is deliberate: it keeps
11+
# hook contents out of the PUBLIC repo (no risk of leaking machine-specific or
12+
# sensitive data). The committed source of truth is the template; run this
13+
# script after cloning to (re)generate the active hook locally.
14+
#
15+
# Replaces the previous embedded-heredoc approach (which had drifted to an old
16+
# version and missed Phase 1b). One source of truth now: the template.
417
#
518
# Usage:
619
# ./scripts/install-git-hooks.sh
7-
#
8-
# This installs the pre-commit hook that validates Claude Code hook JSON formats
920

1021
set -euo pipefail
1122

1223
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1324
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
14-
HOOKS_DIR="$PROJECT_DIR/.git/hooks"
25+
TEMPLATE="$SCRIPT_DIR/pre-commit-template.sh"
1526

1627
echo "Installing git hooks for multi-agent-ralph-loop..."
1728

18-
# Create hooks directory if it doesn't exist
19-
mkdir -p "$HOOKS_DIR"
20-
21-
# Create pre-commit hook
22-
cat > "$HOOKS_DIR/pre-commit" << 'HOOK_EOF'
23-
#!/bin/bash
24-
# pre-commit hook for multi-agent-ralph-loop
25-
# VERSION: 2.57.3
26-
# Purpose: Validate hook JSON formats before commit
27-
#
28-
# CRITICAL FORMAT RULES (per official Claude Code docs):
29-
# - PostToolUse/PreToolUse/UserPromptSubmit: {"continue": true/false}
30-
# - Stop hooks ONLY: {"decision": "approve"/"block"}
31-
# - The string "continue" is NEVER valid for the "decision" field
32-
33-
set -uo pipefail
34-
35-
RED='\033[0;31m'
36-
GREEN='\033[0;32m'
37-
YELLOW='\033[0;33m'
38-
NC='\033[0m'
39-
40-
STAGED_HOOKS=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.claude/hooks/.*\.(sh|py)$' || true)
41-
42-
if [[ -z "$STAGED_HOOKS" ]]; then
43-
exit 0
29+
if [[ ! -f "$TEMPLATE" ]]; then
30+
echo "ERROR: missing template: $TEMPLATE" >&2
31+
exit 1
4432
fi
4533

46-
echo -e "${YELLOW}Pre-commit: Validating Claude Code hook JSON formats${NC}"
47-
48-
ERRORS=0
49-
50-
for hook_file in $STAGED_HOOKS; do
51-
[[ ! -f "$hook_file" ]] && continue
52-
53-
hook_name=$(basename "$hook_file")
54-
55-
# CRITICAL: "decision": "continue" is NEVER valid
56-
if grep -qE '"decision":\s*"continue"' "$hook_file"; then
57-
echo -e "${RED}✗ $hook_name: Uses invalid {\"decision\": \"continue\"}${NC}"
58-
((ERRORS++))
59-
continue
60-
fi
61-
62-
echo -e "${GREEN}✓ $hook_name${NC}"
63-
done
64-
65-
# Phase 1b: PreToolUse permissionDecision enum guard (allow|deny|ask, never "block").
66-
# Regression guard for the "(root): Invalid input" hook error.
67-
PD_CHECK="$(git rev-parse --show-toplevel 2>/dev/null)/scripts/check-pretooluse-permission-decision.sh"
68-
if [[ -n "$STAGED_HOOKS" && -x "$PD_CHECK" ]]; then
69-
if ! "$PD_CHECK" > /dev/null 2>&1; then
70-
echo -e "${RED}✗ Invalid permissionDecision (use \"deny\", not \"block\")${NC}"
71-
echo " Run: $PD_CHECK"
72-
((ERRORS++))
73-
fi
34+
# Resolve the active hooks path. Honor an existing core.hooksPath; otherwise
35+
# adopt the repo convention (.git-hooks) and set it so git actually uses it.
36+
HOOKS_PATH="$(git -C "$PROJECT_DIR" config --get core.hooksPath 2>/dev/null || true)"
37+
if [[ -z "$HOOKS_PATH" ]]; then
38+
HOOKS_PATH=".git-hooks"
39+
git -C "$PROJECT_DIR" config core.hooksPath "$HOOKS_PATH"
40+
echo " core.hooksPath set to $HOOKS_PATH"
7441
fi
7542

76-
if [[ $ERRORS -gt 0 ]]; then
77-
echo -e "${RED}COMMIT BLOCKED: $ERRORS hook(s) have invalid JSON format${NC}"
78-
echo "Reference: tests/HOOK_FORMAT_REFERENCE.md"
79-
exit 1
80-
fi
43+
# Resolve to an absolute directory (core.hooksPath may be relative to repo root).
44+
case "$HOOKS_PATH" in
45+
/*) HOOKS_DIR="$HOOKS_PATH" ;;
46+
*) HOOKS_DIR="$PROJECT_DIR/$HOOKS_PATH" ;;
47+
esac
8148

82-
exit 0
83-
HOOK_EOF
49+
mkdir -p "$HOOKS_DIR"
8450

85-
chmod +x "$HOOKS_DIR/pre-commit"
51+
# Install from the single source of truth. The template already includes
52+
# Phase 1b (PreToolUse permissionDecision guard: allow|deny|ask, never "block").
53+
install -m 0755 "$TEMPLATE" "$HOOKS_DIR/pre-commit"
8654

87-
echo "✓ pre-commit hook installed"
55+
echo "✓ pre-commit installed → $HOOKS_DIR/pre-commit"
56+
echo " source: scripts/pre-commit-template.sh"
57+
echo " active core.hooksPath: $(git -C "$PROJECT_DIR" config --get core.hooksPath)"
8858
echo ""
8959
echo "Git hooks installed successfully!"
90-
echo ""
91-
echo "The pre-commit hook will validate Claude Code hook JSON formats"
92-
echo "before each commit to prevent format errors."
60+
echo "The pre-commit hook validates Claude Code hook JSON formats (incl. the"
61+
echo "PreToolUse permissionDecision enum) before each commit."
9362
echo ""
9463
echo "Reference: tests/HOOK_FORMAT_REFERENCE.md"

0 commit comments

Comments
 (0)