blog.dopana

Back

Imagine writing great code, running git commit, and pushing directly to your remote repository—only for CI/CD to break minutes later due to a missing comma or linting error.

This happens all too often. How can we catch these minor issues locally before they enter the Git commit history? The solution is Git Pre-commit Hooks.

Introduction to Git Pre-commit Hooks#

Git Hooks are built-in scripts that run automatically at key points in the Git workflow (such as before committing, before pushing, or after checking out branches).

The pre-commit hook runs immediately when you execute git commit, right before Git creates the commit object. If the script exits with a non-zero status (!= 0), the commit process is aborted immediately.

graph LR
    A[git commit command] --> B{Pre-commit Hook}
    B -->|Exit code = 0| C[Commit Created]
    B -->|Exit code != 0| D[Abort Commit & Report Error]

The Sharing Problem & core.hooksPath#

By default, Git hooks live inside the hidden .git/hooks/ directory. However, .git is not version-controlled. This introduces two major drawbacks:

  1. Hard to share across teams: Every developer has to copy hook scripts into .git/hooks/ manually.
  2. Easy to forget: New team members joining the project often forget to set up the local hooks.

The core.hooksPath Solution#

Since Git 2.9, Git supports the core.hooksPath configuration, allowing you to point hook execution to any directory.

You can store hooks in a version-controlled folder (e.g., .githooks/) and configure Git:

git config core.hooksPath .githooks
bash

In a Node.js/Bun project, automate this setup for team members using the prepare script in package.json:

package.json
{
  "scripts": {
    "prepare": "git config core.hooksPath .githooks"
  }
}
json

Whenever anyone runs npm install or bun install, Git automatically configures .githooks for the repository.

Practical Example: Auto-formatting Markdown with agent-md#

Here is a real-world pre-commit hook script that checks if agent-md CLI is available, formats staged Markdown files, and re-stages them automatically:

Remember to grant execution permissions to your hook script:

chmod +x .githooks/pre-commit
bash

Bypassing Pre-commit Hooks#

In urgent cases (such as saving a temporary work-in-progress commit before switching branches), you can bypass the hook using --no-verify (or -n):

git commit -m "WIP: temporary save" --no-verify
bash

[!TIP] Use --no-verify sparingly to maintain consistent code quality across your repository.

References#