| name | git-hooks |
| description | This skill should be used when the user asks to "create git hooks", "set up pre-commit hooks", "configure git hooks", "add commit validation", "implement pre-push hooks", or needs guidance on Git hooks implementation, validation scripts, or hook best practices. |
Git Hooks
Automate code quality, testing, and validation using Git hooks—scripts that run automatically at key points in the Git workflow.
What Are Git Hooks
Git hooks are executable scripts that Git runs automatically when specific events occur in a repository. They enable you to:
- Enforce code quality before commits reach the repository
- Run automated tests to catch issues early
- Validate commit messages to maintain consistent standards
- Prevent accidental destructive actions like force pushes
- Trigger CI/CD workflows on push events
- Automate versioning and tagging for releases
Hooks reside in .git/hooks/ by default, but can be version-controlled using core.hooksPath configuration.
Core Concepts
Hook Types
Client-side hooks (run on developer machines):
pre-commit - Before commit is created, validate staged changes
prepare-commit-msg - Populate commit message template
commit-msg - Validate commit message format
post-commit - Notification after successful commit
pre-push - Before pushing to remote, run tests
post-checkout - After checkout, clean up working directory
pre-rebase - Before rebasing, check for conflicts
Server-side hooks (run on remote repository):
pre-receive - Before accepting pushed refs, enforce policies
update - Like pre-receive, but runs per branch
post-receive - After successful push, trigger CI/CD
Hook Lifecycle
Developer action → Git event → Hook script runs → Exit code determines outcome
- Exit 0: Continue with Git operation
- Exit non-zero: Abort Git operation with error message
Making Hooks Executable
Hooks must have execute permissions:
chmod +x .git/hooks/pre-commit
Essential Workflows
Setting Up Version-Controlled Hooks
Git doesn't version-control .git/hooks/ by default. Use core.hooksPath to enable team-wide hooks:
1. Create hooks directory in repository:
mkdir .githooks
2. Configure Git to use custom hooks path:
git config core.hooksPath .githooks
3. Add hooks to version control:
git add .githooks/
git commit -m "Add version-controlled git hooks"
4. Team members run after cloning:
git config core.hooksPath .githooks
This project follows this pattern. See .githooks/ directory for working examples.
Creating a Basic Pre-Commit Hook
Use case: Validate bash scripts before committing.
1. Create hook file:
touch .githooks/pre-commit
chmod +x .githooks/pre-commit
2. Add validation logic:
#!/bin/bash
set -e
echo "🔍 Validating bash scripts..."
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.sh$' || true)
if [ -z "$STAGED_FILES" ]; then
echo "✅ No bash scripts to validate"
exit 0
fi
for file in $STAGED_FILES; do
if [ -f "$file" ]; then
echo "Checking $file..."
bash -n "$file" || exit 1
fi
done
echo "✅ All bash scripts valid"
exit 0
Key patterns:
- Use
set -e to fail fast on errors
- Check for staged files with
git diff --cached
- Exit early if no relevant files
- Provide clear visual feedback with emojis
- Exit with non-zero on validation failure
Modular Hook Architecture
Problem: Single hook file becomes complex with multiple validations.
Solution: Orchestrator pattern that discovers and runs modular hook scripts.
1. Create hooks.d/ directory:
mkdir .githooks/hooks.d
2. Create orchestrator in pre-commit:
#!/bin/bash
set -e
HOOKS_DIR="$(dirname "$0")/hooks.d"
if [ -d "$HOOKS_DIR" ]; then
for hook in "$HOOKS_DIR"/*; do
if [ -x "$hook" ]; then
echo "Running: $(basename "$hook")"
"$hook" || exit 1
fi
done
fi
exit 0
3. Add individual validation scripts:
Naming convention: Use numbered prefixes (10, 20, 30) to control execution order and allow inserting new hooks between existing ones (e.g., add a hypothetical 15-validate-json.sh between 10 and 20).
This project uses this pattern. See .githooks/hooks.d/ for examples.
Validating Commit Messages
Use case: Enforce conventional commit format.
Create commit-msg hook:
#!/bin/bash
COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
PATTERN="^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{10,}$"
if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
echo "❌ Invalid commit message format"
echo ""
echo "Expected format: type(scope): description"
echo "Types: feat, fix, docs, style, refactor, test, chore"
echo "Example: feat(auth): add OAuth2 login support"
exit 1
fi
echo "✅ Commit message valid"
exit 0
Running Tests Before Push
Use case: Prevent pushing broken code to remote.
Create pre-push hook:
#!/bin/bash
set -e
echo "🧪 Running tests before push..."
npm test || {
echo "❌ Tests failed. Push aborted."
exit 1
}
echo "✅ All tests passed"
exit 0
Preventing Force Push to Main
Use case: Protect production branches from destructive operations.
Create pre-push hook:
#!/bin/bash
PROTECTED_BRANCHES="^(main|master|production)$"
while read local_ref local_sha remote_ref remote_sha; do
remote_branch=$(echo "$remote_ref" | sed 's/refs\/heads\///')
if echo "$remote_branch" | grep -qE "$PROTECTED_BRANCHES"; then
if [ "$local_sha" = "0000000000000000000000000000000000000000" ]; then
echo "❌ Deleting $remote_branch is not allowed"
exit 1
fi
if ! git merge-base --is-ancestor "$remote_sha" "$local_sha" 2>/dev/null; then
echo "❌ Force push to $remote_branch is not allowed"
exit 1
fi
fi
done
exit 0
Project-Specific Implementation
This repository implements a production-ready Git hooks system. Follow this implementation pattern for your projects.
Directory Structure
.githooks/
├── pre-commit # Orchestrator script
├── hooks.d/ # Individual validation scripts
│ ├── the-perfect-developer-base-collection-10-validate-bash.sh
│ └── the-perfect-developer-base-collection-20-validate-skills.sh
└── README.md # Documentation
Installation
Automated setup:
./setup-hooks.sh
This configures git config core.hooksPath .githooks automatically.
Hook Naming Convention
Use this pattern for modular hooks:
the-perfect-developer-base-collection-<number>-<description>.sh
- Prefix: Project/team identifier
- Number: Execution order (increments of 10: 10, 20, 30...)
- Description: What the hook validates
Increments of 10 allow inserting new hooks between existing ones (e.g., add 15-validate-json.sh between 10 and 20).
Testing Individual Hooks
Run hooks independently for testing:
.githooks/hooks.d/10-validate-bash.sh
.githooks/pre-commit
chmod -x .githooks/hooks.d/20-validate-skills.sh
mv .githooks/hooks.d/20-validate-skills.sh{,.disabled}
Hook Template
Use this template for new validation hooks:
#!/bin/bash
set -e
echo "🔍 Running [VALIDATION NAME]..."
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep 'pattern' || true)
if [ -z "$STAGED_FILES" ]; then
echo "✅ No files to validate"
exit 0
fi
for file in $STAGED_FILES; do
if [ -f "$file" ]; then
echo "Validating $file..."
fi
done
echo "✅ Validation passed"
exit 0
Hook Parameters and Environment
Common Parameters
Different hooks receive different parameters:
pre-commit: No parameters
#!/bin/bash
STAGED=$(git diff --cached --name-only)
commit-msg: Commit message file path
#!/bin/bash
COMMIT_MSG_FILE=$1
message=$(cat "$COMMIT_MSG_FILE")
pre-push: stdin with refs being pushed
#!/bin/bash
while read local_ref local_sha remote_ref remote_sha; do
done
post-checkout: prev HEAD, new HEAD, branch flag
#!/bin/bash
PREV_HEAD=$1
NEW_HEAD=$2
IS_BRANCH=$3
Environment Variables
Git sets environment variables hooks can access:
GIT_DIR - Path to .git directory
GIT_WORK_TREE - Path to working directory
GIT_INDEX_FILE - Path to index file
GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL - Commit author
GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL - Committer info
Test environment variables:
#!/bin/bash
echo "Running $BASH_SOURCE"
set | grep GIT
echo "PWD is $PWD"
Best Practices
DO:
- Make hooks fast—developers run them frequently
- Exit early when no relevant files are staged
- Provide clear, actionable error messages
- Use visual indicators (✅ ❌ 🔍) for quick scanning
- Version-control hooks using
core.hooksPath
- Test hooks independently before integration
- Document hook behavior in README
- Use modular architecture for complex validation
- Make hooks easy to temporarily disable
DON'T:
- Perform long-running operations in pre-commit
- Block commits without clear explanation
- Hardcode file paths—use relative paths
- Assume dependencies are installed—check first
- Create infinite loops or complex recursion
- Mix multiple concerns in one hook script
- Skip error handling and validation
- Force hooks on team without consensus
Security Considerations
Validate inputs: Never trust user input in hooks
git diff --cached --name-only | xargs some-command
git diff --cached --name-only | while read file; do
if [ -f "$file" ]; then
some-command "$file"
fi
done
Check permissions: Ensure hooks are executable by intended users only
chmod 755 .githooks/pre-commit
Avoid secrets: Never hardcode credentials in hooks
API_TOKEN="secret123"
API_TOKEN="${API_TOKEN:-$(cat ~/.api_token)}"
Review third-party hooks: Understand what scripts do before using them.
Troubleshooting
Hook not running:
- Check execute permissions:
ls -l .githooks/pre-commit
- Verify
core.hooksPath config: git config core.hooksPath
- Ensure file has no
.sample extension
- Check shebang line is correct:
#!/bin/bash
Hook runs but fails unexpectedly:
- Run hook manually to see errors:
.githooks/pre-commit
- Check exit codes:
echo $? after running
- Verify dependencies are installed
- Test with minimal staged changes
- Add debug output:
set -x at start of script
Cannot commit:
- Read hook error message carefully
- Fix validation issues or unstage problematic files
- Temporarily disable hook if needed:
git commit --no-verify
- Note:
--no-verify bypasses all hooks—use sparingly
Hooks not version-controlled:
- Ensure using
core.hooksPath, not .git/hooks/
- Verify
.githooks/ is committed: git ls-files .githooks/
- Check team members ran setup:
git config core.hooksPath
Additional Resources
Reference Documentation
For detailed information on advanced topics:
references/hook-types.md - Complete list of all Git hooks with parameters and use cases
references/server-side-hooks.md - Server-side hooks for CI/CD and policy enforcement
references/advanced-patterns.md - Complex validation patterns and techniques
references/ci-cd-integration.md - Integrating hooks with continuous integration pipelines
Example Implementations
Working examples from this project:
examples/modular-pre-commit/ - Orchestrator pattern with hooks.d/ directory
examples/validate-bash.sh - Bash syntax validation hook
examples/validate-skills.sh - SKILL.md validation hook
examples/setup-hooks.sh - Installation script for team setup
External Resources
Official Git documentation:
Comprehensive tutorials:
Quick Reference
Configure version-controlled hooks:
git config core.hooksPath .githooks
Make hook executable:
chmod +x .githooks/pre-commit
Test hook manually:
.githooks/pre-commit
Bypass hooks temporarily:
git commit --no-verify
Common hook skeleton:
#!/bin/bash
set -e
echo "🔍 Running validation..."
echo "✅ Validation passed"
exit 0
Get staged files:
git diff --cached --name-only --diff-filter=ACM
Validate and exit on failure:
command || exit 1