Git hooks automate checks and actions at key points in your workflow. When they stop running, you lose validation, automated testing, and code quality enforcement.
The Problem
You have hooks configured, but they're not executing:
git commit -m "test"
# Hook should run but doesn't
# Commit succeeds without validationOr hooks that used to work suddenly stopped.
Diagnosis Steps
Check if hooks exist:
``bash
ls -la .git/hooks/
You should see files like:
``
pre-commit
pre-push
commit-msg
post-merge
Check if hooks are executable:
``bash
ls -l .git/hooks/pre-commit
Should show execute permissions:
``
-rwxr-xr-x 1 user staff 432 Jan 1 12:00 pre-commit
Check hook content:
``bash
cat .git/hooks/pre-commit
Test hook manually:
``bash
.git/hooks/pre-commit
echo $? # Should print 0 for success
Check Git version:
``bash
git --version
Some hook features require newer Git versions.
Check if hooks are ignored:
``bash
git config --get core.hooksPath
Verify Git directory location:
``bash
git rev-parse --git-dir
Solution 1: Missing Execute Permission
The most common cause. Hooks must be executable.
Add execute permission:
``bash
chmod +x .git/hooks/pre-commit
chmod +x .git/hooks/pre-push
chmod +x .git/hooks/commit-msg
Fix all hooks at once:
``bash
chmod +x .git/hooks/*
Verify:
``bash
ls -la .git/hooks/pre-commit
Should show x in permissions.
Solution 2: Wrong Shebang Line
Hook scripts must start with a valid shebang:
Check shebang:
``bash
head -1 .git/hooks/pre-commit
Should show:
``bash
#!/bin/sh
or
bash
#!/bin/bash
or
bash
#!/usr/bin/env node
Fix incorrect shebang: ```bash # For bash scripts sed -i '1s|^.*|#!/bin/bash|' .git/hooks/pre-commit
# For Node.js scripts sed -i '1s|^.*|#!/usr/bin/env node|' .git/hooks/pre-commit ```
Solution 3: Hook Not in Correct Location
Verify hooks directory:
``bash
git rev-parse --git-dir
ls -la $(git rev-parse --git-dir)/hooks/
For worktrees:
Hooks are stored in the main repository, not in worktrees:
``bash
git worktree list
# Find the main worktree
ls -la /path/to/main/worktree/.git/hooks/
For submodules:
Each submodule has its own .git directory:
``bash
cd submodule/
ls -la .git/hooks/
Solution 4: Custom Hooks Path Configuration
Git can be configured to use a different hooks directory.
Check current hooks path:
``bash
git config --get core.hooksPath
If this returns a path, hooks are being loaded from there instead of .git/hooks/.
Reset to default:
``bash
git config --unset core.hooksPath
Or set to custom path intentionally:
``bash
git config core.hooksPath .githooks
Then place hooks in .githooks/:
``bash
mkdir -p .githooks
mv .git/hooks/pre-commit .githooks/
chmod +x .githooks/pre-commit
Solution 5: Using Husky or Other Hook Managers
If using Husky or similar, hooks work differently.
Check for Husky:
``bash
cat package.json | grep husky
ls -la .husky/
Husky v8+ setup:
``bash
npx husky install
npx husky add .husky/pre-commit "npm test"
Ensure prepare script exists:
``json
// package.json
{
"scripts": {
"prepare": "husky install"
}
}
Reinstall hooks:
``bash
npm run prepare
For pre-commit framework:
``bash
pre-commit install
pre-commit run --all-files
Solution 6: Hook Script Has Errors
Test the hook independently:
Run hook directly:
``bash
.git/hooks/pre-commit
Debug with verbose output:
``bash
bash -x .git/hooks/pre-commit
Common script errors:
Missing newline at end:
``bash
# Fix missing newline
echo "" >> .git/hooks/pre-commit
Path issues in script:
``bash
# Use absolute paths or cd to repo root
cd "$(git rev-parse --show-toplevel)"
Exit code issues:
``bash
# Hook must exit with 0 to allow commit
# Non-zero exit blocks the commit
Solution 7: Git Not Finding the Hook
Verify exact hook name:
``bash
ls -la .git/hooks/
Hook names must match exactly:
- pre-commit (not pre-commit.sh)
- pre-push
- commit-msg
- post-merge
- pre-rebase
Sample hook names:
``
.git/hooks/pre-commit # Correct
.git/hooks/pre-commit.sample # Only sample, not active
.git/hooks/pre-commit.sh # Wrong - .sh extension
Remove .sample extension:
``bash
mv .git/hooks/pre-commit.sample .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
Solution 8: Global Hooks Overriding Local
Global hooks might be interfering:
Check global hooks:
``bash
git config --global --get core.hooksPath
ls -la $(git config --global --get core.hooksPath 2>/dev/null || echo "$HOME/.git/hooks")
To use local hooks preferentially:
``bash
git config --unset core.hooksPath
Solution 9: Windows-Specific Issues
On Windows, line endings and executable bits differ:
Fix line endings:
``bash
dos2unix .git/hooks/pre-commit
Or in Git Bash:
``bash
sed -i 's/\r$//' .git/hooks/pre-commit
Fix permissions on Windows:
``bash
git update-index --chmod=+x .git/hooks/pre-commit
Verify bash is available:
``bash
which bash
Update shebang if needed:
``bash
#!/usr/bin/env bash
Solution 10: Debugging Hook Execution
Enable Git trace:
``bash
GIT_TRACE=1 git commit -m "test" 2>&1 | grep hook
Add debug output to hook: ```bash #!/bin/bash set -x # Echo commands exec 2> /tmp/hook-debug.log
# Your hook code echo "Running pre-commit hook" >&2 ```
Test with sample hook:
``bash
echo '#!/bin/bash
echo "Hook is running!"
exit 0' > .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
git commit --allow-empty -m "test"
Verification
Test the hook works:
``bash
# Make a change that should fail the hook
git commit -m "test"
# Should see hook output
Verify hook blocks on failure:
``bash
# Modify hook to exit 1
echo 'exit 1' >> .git/hooks/pre-commit
git commit -m "test"
# Should be blocked
Fix hook and verify success:
``bash
# Restore proper hook
echo '#!/bin/bash
exit 0' > .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
git commit -m "test"
# Should succeed
Common Hook Templates
Pre-commit:
``bash
#!/bin/bash
# Run tests before commit
npm test
RESULT=$?
if [ $RESULT -ne 0 ]; then
echo "Tests failed. Commit aborted."
exit 1
fi
exit 0
Commit-msg:
``bash
#!/bin/bash
# Validate commit message format
MSG_FILE=$1
MSG=$(cat "$MSG_FILE")
if ! echo "$MSG" | grep -qE "^(feat|fix|docs|style|refactor|test|chore):"; then
echo "Commit message must start with feat|fix|docs|style|refactor|test|chore:"
exit 1
fi
exit 0
Pre-push:
``bash
#!/bin/bash
# Prevent force push to main
while read local_ref local_sha remote_ref remote_sha; do
if [ "$remote_ref" = "refs/heads/main" ]; then
if [ "$local_sha" != "$remote_sha" ] && ! git merge-base --is-ancestor $remote_sha $local_sha; then
echo "Force push to main is not allowed"
exit 1
fi
fi
done
exit 0
Best Practices
- 1.Version control your hooks - Store in
.githooks/and symlink - 2.Use hook managers - Husky for Node, pre-commit for Python
- 3.Make hooks fast - Long hooks slow down commits
- 4.Document hook requirements - Add README for team members
- 5.Test hooks in CI - Don't rely solely on local hooks