Git & CI/CD Intermediate

Troubleshooting Git Pre-Commit Hook Failure (Bash) on WSL2 Ubuntu

Resolve 'pre-commit failed execution' errors for Bash Git hooks on Windows WSL2 Ubuntu, often caused by line endings or permissions.

👨‍💻
Senior Systems Architect • Verified in Staging Labs

Resolve 'pre-commit failed execution' errors for Bash Git hooks on Windows WSL2 Ubuntu, often caused by line endings or permissions.

Introduction

Encountering a "Git commit hooks pre-commit failed execution" error when working within a Windows Subsystem for Linux 2 (WSL2) environment can be a frustrating roadblock in your development workflow. This issue typically prevents git commit operations from completing successfully, often pointing to problems with the pre-commit hook script itself, particularly when dealing with Bash scripts and the nuances of cross-OS environments like Windows and Linux. This guide provides a comprehensive, expert-level approach to diagnose and resolve such failures, focusing on common pitfalls unique to WSL2.

Symptom & Error Signature

When attempting to run git commit, the operation will halt, displaying an error message similar to the following in your WSL2 terminal:

$ git commit -m "My commit message"
.git/hooks/pre-commit: 1: .git/hooks/pre-commit: #!/bin/bash: not found
.git/hooks/pre-commit: 2: Syntax error: word unexpected (expecting ")")

or

$ git commit -m "My commit message"
husky > pre-commit (node v18.17.0)
.git/hooks/pre-commit: line 7: ./scripts/run-linter.sh: Permission denied
husky > pre-commit hook failed (code 1)

or a more generic "hook failed" output:

$ git commit -m "My commit message"
husky > pre-commit (node v18.17.0)
Some error output from your script...
husky > pre-commit hook failed (code 1)

The key indicators are the "not found", "Syntax error", "Permission denied", or "hook failed (code 1)" messages originating from the pre-commit hook or a script it calls.

Root Cause Analysis

The "Git commit hooks pre-commit failed execution error" in a WSL2 Ubuntu environment, particularly for Bash scripts, almost invariably boils down to one or a combination of the following issues:

  1. Incorrect Line Endings (CRLF vs. LF): This is by far the most common culprit. Windows uses Carriage Return and Line Feed (CRLF) for line endings, while Unix-like systems (including Ubuntu in WSL2) use only Line Feed (LF). If a Bash script is created or edited using a Windows-native editor that saves with CRLF endings, the Linux interpreter will misinterpret the r character. For instance, #!/bin/bash might be seen as #!/bin/bashr, leading to a "command not found" or "bad interpreter" error because #!/bin/bashr does not exist as an interpreter.

  2. Insufficient Script Permissions: For any Bash script to be executable on Linux, it must have execute permissions (chmod +x). If the pre-commit hook (or any script it calls) lacks these permissions, the system will return a "Permission denied" error when Git tries to run it. This can occur if files are copied from Windows without retaining executable bits or if default permissions are restrictive.

  3. Incorrect Shebang (Interpreter Path): The shebang line #!/bin/bash specifies the interpreter for the script. If bash is located at a different path (e.g., /usr/bin/bash), or if the shebang itself is malformed (often due to line ending issues), the system won't know how to execute the script.

  4. PATH Environment Variable Issues: Git hooks run in a relatively minimal environment. If your script relies on commands or executables that are not in the default PATH during hook execution, it will fail with "command not found" errors. This is especially pertinent if the script works fine when run manually but fails as a hook.

  5. Script Syntax Errors or Runtime Failures: While less common for the "failed execution" of the hook itself (rather than the commands within it failing), a fundamental syntax error in the Bash script can prevent its initial execution. More often, a command within the hook fails (e.g., a linter or formatter), causing the hook to exit with a non-zero status, which Git interprets as a failure.

  6. WSL2 Disk Mounts and core.filemode: If your Git repository resides on a Windows drive (e.g., /mnt/c/Users/YourUser/Repo) mounted within WSL2, handling file permissions can sometimes be tricky. While chmod +x usually works, discrepancies in how Windows and Linux handle file metadata can sometimes lead to issues. Git's core.filemode setting can influence this, though it's less frequently the direct cause of hook execution failure itself.

Step-by-Step Resolution

Follow these steps to diagnose and resolve your Git pre-commit hook failure. Start with the most common issues first.

1. Verify and Correct Script Permissions

Ensure your pre-commit hook script, and any other scripts it calls, are executable.

  1. Navigate to your Git hooks directory:
    cd .git/hooks
    
  2. Check current permissions:
    ls -l pre-commit
    
    Look for an x (execute) permission for the owner (-rwx or rwxr-xr-x). If it's missing (e.g., -rw-r--r--), you need to add execute permissions.
  3. Add execute permissions:
    chmod +x pre-commit
    

    Apply chmod +x to any other custom scripts called by your pre-commit hook (e.g., ./scripts/run-linter.sh in the example error).

2. Check and Correct Line Endings (CRLF to LF)

This is often the primary culprit for "bad interpreter" or "not found" errors on the shebang line.

  1. Check the file's line endings:

    file pre-commit
    
    • Expected (correct) output: pre-commit: Bourne-Again shell script, ASCII text executable or pre-commit: a /bin/bash script, ASCII text executable
    • Problematic (CRLF) output: pre-commit: Bourne-Again shell script, ASCII text executable, with CRLF line terminators or pre-commit: a /bin/bash script, ASCII text executable, with CRLF line endings The presence of "CRLF" confirms the issue.
  2. Convert line endings from CRLF to LF: You can use dos2unix, sed, or your text editor.

    Option A: Using dos2unix (Recommended) If dos2unix is not installed, install it:

    sudo apt update
    sudo apt install dos2unix
    

    Then convert the file:

    dos2unix pre-commit
    

    If pre-commit calls other scripts, ensure those also have their line endings converted.

    Option B: Using sed

    sed -i 's/r$//' pre-commit
    

    This command removes carriage return characters (r) at the end of lines.

    Option C: Using a Text Editor (e.g., VS Code)

    • Open pre-commit in a text editor like VS Code within WSL2 (e.g., code .git/hooks/pre-commit).
    • In the bottom right status bar, click on "CRLF" and change it to "LF". Save the file.

    Always check line endings for any Bash script files that are executed as part of your Git hooks, especially if they are created or edited on the Windows side.

3. Validate Shebang (Interpreter Path)

Ensure the first line of your script (#!) correctly points to the bash interpreter.

  1. Find the bash interpreter path in your WSL2 environment:
    which bash
    
    This typically returns /usr/bin/bash or /bin/bash.
  2. Verify the shebang line: Open pre-commit in a text editor (e.g., nano .git/hooks/pre-commit or code .git/hooks/pre-commit). The very first line should match the output of which bash:
    #!/usr/bin/bash
    
    or
    #!/bin/bash
    

    Using #!/usr/bin/env bash is often more portable, as env will search the PATH for bash.

4. Debugging the Hook Script's Content

If the hook is executable and has correct line endings/shebang, the issue lies within the script's logic.

  1. Enable verbose debugging: Add the following lines at the beginning of your pre-commit script (just after the shebang):

    #!/bin/bash
    set -euxo pipefail # Add these lines for debugging
    
    • set -e: Exit immediately if a command exits with a non-zero status.
    • set -u: Treat unset variables as an error and exit.
    • set -x: Print commands and their arguments as they are executed.
    • 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 in the pipeline exit successfully. These flags will make your script extremely verbose, showing exactly what commands are run and where it fails.
  2. Manually execute the hook: From your repository root, try running the hook manually:

    bash .git/hooks/pre-commit
    

    Observe the output carefully for any errors, especially those indicating "command not found" or syntax issues.

  3. Isolate problematic commands: If the script is long, comment out sections or add echo statements to narrow down the failure point. For example:

    #!/bin/bash
    set -euxo pipefail
    
    echo "Running step 1: Linting"
    npx eslint . --fix
    echo "Step 1 complete."
    
    echo "Running step 2: Formatting"
    npx prettier --write .
    echo "Step 2 complete."
    
    # ... and so on
    

5. Environment and PATH Considerations

Git hooks run in a simpler environment than your interactive shell. Commands you can run manually might not be in the hook's PATH.

  1. Check the PATH within the hook: Add echo "PATH: $PATH" to your pre-commit script to see what paths are available.
  2. Use absolute paths: Instead of mycommand, use /usr/local/bin/mycommand.
  3. Source your shell profile (use with caution): If your script needs environment variables defined in your .bashrc or .profile, you can source them, but this can slow down the hook and introduce unwanted variables.
    #!/bin/bash
    source ~/.bashrc # Or ~/.profile
    
    # Rest of your hook script
    

    Sourcing shell profiles can sometimes introduce interactive elements or slow down your hook significantly. Prefer absolute paths or explicit variable definitions if possible.

6. WSL2 Specific: git config core.filemode

If your Git repository is located on a Windows drive mounted in WSL2 (e.g., /mnt/c/Users/YourUser/MyRepo), file permission changes made with chmod might not be persistently recognized by Git or the underlying filesystem in the way native Linux files are.

  1. Check repository location:
    pwd
    
    If it starts with /mnt/c/ (or similar), it's on a Windows drive.
  2. Disable Git's file mode tracking: Git typically tracks executable bit changes. On mixed filesystems, this can sometimes cause issues. Disabling it tells Git to ignore changes to the executable bit.
    git config core.filemode false
    
    This command applies to the current repository. You can add --global to apply it to all repositories.

    Setting core.filemode false means Git will no longer track changes to file permissions. Be mindful of this if you have executable scripts in your repository that should have their permissions managed by Git (e.g., shell scripts, build tools).

By systematically working through these steps, you should be able to pinpoint and resolve the pre-commit hook execution failure in your WSL2 Ubuntu environment. Remember to test after each significant change.

👨‍💻

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.