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:

bash
git commit -m "test"
# Hook should run but doesn't
# Commit succeeds without validation

Or 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. 1.Version control your hooks - Store in .githooks/ and symlink
  2. 2.Use hook managers - Husky for Node, pre-commit for Python
  3. 3.Make hooks fast - Long hooks slow down commits
  4. 4.Document hook requirements - Add README for team members
  5. 5.Test hooks in CI - Don't rely solely on local hooks