Git & CI/CD Intermediate

Resolving Git pre-commit Hook Failed Execution Errors on Ubuntu 20.04 LTS

Troubleshoot Git pre-commit hook failures on Ubuntu 20.04 LTS, often due to permissions, shebang, or script errors. Restore your commit workflow.

👨‍💻
Senior Systems Architect • Verified in Staging Labs

Troubleshoot Git pre-commit hook failures on Ubuntu 20.04 LTS, often due to permissions, shebang, or script errors. Restore your commit workflow.

A failing Git pre-commit hook can halt your development workflow, preventing commits from being made and potentially disrupting CI/CD pipelines. This guide provides a highly technical, step-by-step approach to diagnose and resolve pre-commit hook execution errors specifically on Ubuntu 20.04 LTS, focusing on common bash-related issues. You'll learn to identify the root cause, from permission problems and incorrect shebangs to script logic errors and environment PATH discrepancies.

Symptom & Error Signature

When attempting to git commit, the process aborts, and you observe error messages in your terminal indicating that the pre-commit hook failed. The exact output can vary depending on the specific cause and whether you are using a hook manager like Husky.

Here are typical error signatures you might encounter:

Generic Bash Execution Failure

$ git commit -m "feat: Add new feature"
fatal: cannot run .git/hooks/pre-commit: No such file or directory
error: git-sh-setup: failed to find script directory in PATH

Permission Denied / Not Executable

$ git commit -m "fix: Resolve bug"
hint: The 'pre-commit' hook was ignored because it's not set as executable.
hint: You can change the permissions of the file by
hint:   chmod +x .git/hooks/pre-commit
fatal: cannot run .git/hooks/pre-commit: Permission denied

Syntax Error within the Hook Script

$ git commit -m "refactor: Improve performance"
/home/user/my-project/.git/hooks/pre-commit: line 5: unexpected token `then'
/home/user/my-project/.git/hooks/pre-commit: line 5: `if [ "$1" == "--no-verify" ]; then'

External Tool Not Found (PATH Issue or Missing Dependency)

$ git commit -m "docs: Update README"
/home/user/my-project/.git/hooks/pre-commit: line 10: prettier --write: command not found
husky > pre-commit hook failed (exit 1)

Husky Specific Error (if used)

$ git commit -m "chore: Setup lint-staged"
husky > pre-commit (node v14.x)
env: node: No such file or directory
husky > pre-commit hook failed (exit 1)

Root Cause Analysis

The pre-commit hook is a simple executable script located at .git/hooks/pre-commit within your repository. Its failure to execute successfully can stem from several underlying issues:

  1. Incorrect File Permissions: The most common cause. The hook script must have executable permissions (+x) for the Git client to run it.
  2. Missing or Incorrect Shebang: The first line of a shell script, the "shebang" (e.g., #!/bin/bash), tells the operating system which interpreter to use. If it's missing, points to a non-existent interpreter, or has incorrect syntax, the script won't run.
  3. Bash Script Syntax Errors: Errors within the script itself (e.g., typos, unclosed quotes, incorrect conditional statements) will cause the interpreter to fail.
  4. Environment PATH Discrepancies: The PATH variable inside a Git hook's execution environment might differ from your interactive shell, leading to "command not found" errors for tools like node, npm, prettier, eslint, etc., that the hook relies on.
  5. Missing External Dependencies: The hook script might call external programs (e.g., jq, prettier, lint-staged) that are not installed on the system or are not globally available in the hook's PATH.
  6. Incorrect Line Endings: If the script was created or edited on a Windows system, it might have CRLF line endings. On Linux, scripts typically expect LF line endings, and CRLF can cause parsing issues with the shebang or other commands.
  7. Symlink Issues: If the pre-commit file is a symbolic link to another script (common with global hooks or framework setups), the target of the symlink might be broken or incorrect.
  8. Husky/Hook Manager Configuration: If using a tool like Husky, its configuration (.husky/pre-commit or package.json scripts) might be misconfigured, or the underlying Node.js environment might be missing or inaccessible.

Step-by-Step Resolution

Follow these steps to diagnose and resolve your pre-commit hook execution errors. We will assume the problematic hook is located at .git/hooks/pre-commit. Adjust paths if you're using a hook manager like Husky (e.g., .husky/pre-commit).

1. Verify Executable Permissions

The hook script must be executable. This is the most frequent cause of failure.

  1. Check current permissions:

    ls -l .git/hooks/pre-commit
    

    Look for an x in the permission string (e.g., -rwxr-xr-x). If x is missing (e.g., -rw-r--r--), it's not executable.

  2. Make the script executable:

    chmod +x .git/hooks/pre-commit
    
  3. Retry your commit:

    git commit -m "Test after permissions fix"
    

    If it now works, you're done! Otherwise, proceed to the next step.

2. Inspect the Shebang Line

The shebang #!/bin/bash tells the system to use bash to execute the script.

  1. Open the hook script:

    nano .git/hooks/pre-commit
    # Or your preferred editor, e.g., vim .git/hooks/pre-commit
    
  2. Verify the first line: It should start with #!. Common valid shebangs for bash scripts include:

    #!/bin/bash
    

    Or, for better portability across different Linux distributions where bash might not be directly in /bin:

    #!/usr/bin/env bash
    

    Ensure there are no spaces or characters before the #!. Also, verify that the specified interpreter (e.g., /bin/bash or bash for env) actually exists on your system via which bash.

3. Debug Script Logic and Syntax

If permissions and shebang are correct, the problem lies within the script's logic or syntax.

  1. Enable Debugging Output: Add set -euxo pipefail right after the shebang line in your pre-commit script.

    • set -e: Exit immediately if a command exits with a non-zero status.
    • set -u: Treat unset variables as an error.
    • set -x: Print commands and their arguments as they are executed (very verbose, but useful).
    • set -o pipefail: The return value of a pipeline is the status of the last command to exit with a non-zero status, or zero if all commands exit successfully.
    #!/bin/bash
    set -euxo pipefail # Add this line
    
    # Your existing hook script logic follows...
    
  2. Manually Run the Hook for Detailed Output: Navigate to your repository's root and execute the hook directly. This will show you exactly what the script is trying to do and where it might be failing, especially with set -x enabled.

    cd /path/to/your/repository
    ./.git/hooks/pre-commit
    

    If the hook requires staged files to test its logic, you'll need to stage some changes before running it manually.

    If the error points to a specific line number (e.g., line 5: unexpected token), focus your debugging efforts around that line in the script.

4. Investigate PATH and Environment Variables

Tools like npm, node, prettier, eslint, etc., might not be found because the PATH within the hook's execution environment is limited.

  1. Echo PATH inside the hook: Temporarily add echo "PATH: $PATH" to your pre-commit script to see what PATH Git provides.

    #!/bin/bash
    echo "DEBUG: PATH=$PATH"
    # ... rest of your script
    

    Commit and observe the output. If essential directories are missing (e.g., /usr/local/bin for Node.js), that's your problem.

  2. Explicitly source environment variables (if necessary): If your project relies on nvm or other environment managers, you might need to source their setup scripts within the hook.

    #!/bin/bash
    # For NVM users, ensure node/npm is available
    export NVM_DIR="$HOME/.nvm"
    [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"  # This loads nvm
    [ -s "$NVM_DIR/bash_completion" ] && . "$NVM_DIR/bash_completion" # This loads nvm bash_completion
    
    # Your existing hook script logic follows...
    # Now commands like 'node' or 'npm' should be in PATH
    
  3. Use absolute paths for critical commands: As a workaround or for enhanced robustness, you can replace prettier with /usr/local/bin/prettier (or its absolute path determined by which prettier).

    # Instead of:
    # prettier --write .
    # Use:
    $(which prettier) --write .
    

    This ensures the exact binary is called.

5. Install Missing External Dependencies

If your hook uses a tool (e.g., jq, xmlstarlet, hadolint, markdownlint) that is not installed, it will fail with a "command not found" error.

  1. Identify the missing command: The error message (e.g., prettier: command not found) usually indicates this.

  2. Install the dependency:

    # Example for jq:
    sudo apt update
    sudo apt install jq
    
    # Example for a Node.js-based tool (like Prettier, ESLint, Lint-Staged)
    # Ensure Node.js and npm are installed first, then install globally or locally
    npm install -g prettier # Global install
    # or
    npm install --save-dev prettier # Project-local install (then ensure hook references local binary, e.g., ./node_modules/.bin/prettier)
    

6. Correct Line Endings

Windows line endings (CRLF) can cause issues on Linux systems.

  1. Convert line endings:

    sudo apt install dos2unix # Install if not already present
    dos2unix .git/hooks/pre-commit
    
  2. Verify conversion: Open the file in a text editor like nano or vim and check for ^M characters if they were present.

7. Check Symlink Integrity

If your pre-commit is a symlink, ensure its target is valid.

  1. Check if it's a symlink:

    ls -l .git/hooks/pre-commit
    # Output like: lrwxrwxrwx ... .git/hooks/pre-commit -> ../../hooks/pre-commit.sh
    
  2. Resolve the symlink target:

    readlink -f .git/hooks/pre-commit
    

    This command will output the absolute path to the actual script file. Verify this path is correct and the target file exists and is executable. If the symlink is broken, recreate it.

    # Example: Recreate a symlink
    rm .git/hooks/pre-commit
    ln -s ../../hooks/pre-commit.sh .git/hooks/pre-commit
    chmod +x .git/hooks/pre-commit # Ensure target and symlink are executable
    

8. Temporarily Bypass the Hook (Use with Caution)

Bypassing commit hooks can lead to unlinted, untested, or incorrectly formatted code entering your repository. Only use this as a temporary measure for urgent commits or during deep debugging.

If you absolutely need to make a commit and cannot immediately fix the hook, you can bypass it:

git commit -m "Emergency commit (hook bypassed)" --no-verify
# Or the shorter form:
git commit -m "Emergency commit (hook bypassed)" -n
👨‍💻

Johnathon Wheeler

Senior Systems Architect & DevOps Engineer • Austin, TX

Connect on LinkedIn

Johnathon has over 16 years of hands-on experience designing, debugging, and scaling Linux web hosting stacks, container clusters, and high-availability database architectures. Every guide on ButItWorkedLocal is independently tested against Debian 12, Ubuntu 24.04/22.04 LTS, Rocky Linux, and Docker environments to guarantee reproducibility in production.

🛡️

Our Production Verification Guarantee

Encountering a bug not covered here or running a non-standard kernel configuration? Our solutions are continually refined against real production incidents. Submit an environment trace for our editorial team to replicate.