The post-checkout hook script in your Git repository exited with a non-zero status code, indicating the hook failed during checkout, clone, or branch switch operations. This typically occurs due to permission issues, missing dependencies, or script errors.
When Git displays "error: post-checkout hook exited with error code", it means a script located at `.git/hooks/post-checkout` (or configured via core.hooksPath) ran after a checkout operation and returned a non-zero exit status. The post-checkout hook is triggered automatically after `git checkout`, `git switch`, `git clone` (unless --no-checkout is used), and `git worktree add`. Unlike pre-commit hooks, the post-checkout hook cannot prevent the checkout from completingโbut its exit status becomes the exit status of the git command, which can cause CI/CD pipelines or scripts to fail. Common post-checkout hooks include automatic dependency installation (npm install, pip install), environment setup, file permission changes, or triggering build processes. When any command within the hook script fails, the entire hook returns an error.
First, locate where Git is looking for hooks. By default, hooks are in .git/hooks/, but this can be overridden:
# Check if a custom hooks path is configured
git config core.hooksPath
# If no output, hooks are in .git/hooks/
ls -la .git/hooks/post-checkoutIf the hook is managed by a tool like Husky, check .husky/post-checkout instead.
Git will not run hook scripts that lack execute permissions:
# Check current permissions
ls -la .git/hooks/post-checkout
# Make the hook executable
chmod +x .git/hooks/post-checkoutOn Windows, ensure the script has the correct line endings (LF, not CRLF):
# Convert CRLF to LF
sed -i 's/\r$//' .git/hooks/post-checkoutExecute the hook script directly to see detailed error output. The post-checkout hook receives three arguments: previous HEAD, new HEAD, and a flag (1 for branch checkout, 0 for file checkout):
# Run the hook manually with typical arguments
.git/hooks/post-checkout HEAD HEAD 1
# Or with bash for more verbose output
bash -x .git/hooks/post-checkout HEAD HEAD 1This reveals exactly which command is failing and why.
Hooks run in a minimal environment without your shell profile. If your hook uses node, python, or ruby managed by version managers, add the PATH explicitly:
#!/bin/bash
# Add at the top of your post-checkout hook
# For nvm (Node.js)
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# For pyenv (Python)
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
# For rbenv (Ruby)
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"For Husky users, create ~/.huskyrc with these exports instead.
Make your hook script more robust by handling errors gracefully:
#!/bin/bash
set -e # Exit on first error
# Only run npm install if package.json changed
changedFiles="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD 2>/dev/null || true)"
if echo "$changedFiles" | grep -q "package.json"; then
echo "package.json changed, running npm install..."
npm install || {
echo "Warning: npm install failed, but continuing..."
exit 0 # Don't fail the checkout
}
fi
exit 0This prevents non-critical hook failures from breaking your workflow.
If you need to proceed despite hook failures, you can skip hooks:
# For git clone, skip checkout hooks
git clone --no-checkout <repo-url>
cd <repo>
git checkout main
# Or set environment variable to allow hooks during clone (Git 2.39+)
GIT_CLONE_PROTECTION_ACTIVE=false git clone <repo-url>Note: --no-verify only skips pre-commit and commit-msg hooks, not post-checkout hooks. The only way to skip post-checkout is to temporarily rename or remove the hook file.
Add debugging to your hook script to trace execution:
#!/bin/bash
# Enable debugging
set -x # Print each command before executing
exec 2>&1 # Redirect stderr to stdout
echo "Post-checkout hook started"
echo "Previous HEAD: $1"
echo "New HEAD: $2"
echo "Branch checkout: $3"
echo "PATH: $PATH"
echo "Working directory: $(pwd)"
# Your hook commands here...
echo "Post-checkout hook completed"Check the terminal output after checkout to identify the failing command.
Post-checkout vs Post-merge hooks: The post-checkout hook runs after git checkout and git clone, while post-merge runs after git merge and git pull. If you want to run npm install after pulling changes, you need both hooks, or use a tool like Husky that manages this for you.
Hook exit status behavior: Unlike pre-commit hooks that can abort operations, post-checkout hooks run after the checkout is complete. The hook's exit status only affects the exit status of the git command itselfโthe checkout has already happened. This is important for CI/CD: the files are correct, but your pipeline might report failure.
Security considerations in Git 2.39+: Recent Git versions block hooks during clone by default for security. If you see "fatal: active post-checkout hook found during git clone", this is intentional protection against malicious repositories. Only use GIT_CLONE_PROTECTION_ACTIVE=false for trusted repositories.
Performance tip: Post-checkout hooks run on every checkout, including switching between branches you've used before. Avoid expensive operations like npm install unless dependencies actually changed. Check if package.json or lockfiles were modified before running installation commands.
kex_exchange_identification: Connection closed by remote host
Connection closed by remote host when connecting to Git server
fatal: unable to access: Proxy auto-configuration failed
How to fix 'Proxy auto-configuration failed' in Git
fatal: unable to access: Authentication failed (proxy requires basic auth)
How to fix 'Authentication failed (proxy requires basic auth)' in Git
fatal: unable to access: no_proxy configuration not working
How to fix 'no_proxy configuration not working' in Git
fatal: unable to read tree object in treeless clone
How to fix 'unable to read tree object in treeless clone' in Git