Git Hooks: Automate Checks Before Commit and Push

Khimananda Oli 5 min read Virtualization
Git Hooks: Automate Checks Before Commit and Push

By Khimananda Oli | Last reviewed: August 2026

Broken builds, leaked secrets, and non-compliant commit messages waste engineering time and erode trust in production systems. Implementing Git Hooks: Automate Checks Before Commit and Push moves validation left, catching errors on the developer's machine before they ever reach the remote repository or CI pipeline. This guide covers the practical configuration of client-side hooks using modern tooling like Husky and pre-commit, ensuring your team maintains high standards without relying solely on server-side rejections.

What Are Git Hooks and Why Automate Checks Before Commit?

Git hooks are executable scripts triggered automatically by Git events. They reside in the .git/hooks directory but are not version-controlled by default, which is why we use wrapper tools to share them across teams. When you understand Git Hooks: Automate Checks Before Commit and Push, you effectively create a local quality gate that mirrors your CI/CD pipeline. For teams adopting CI/CD best practices for small teams, this reduces feedback loops from minutes to seconds.

DeveloperLocal Git Hooks• pre-commit (lint/format)• commit-msg (conventional)• pre-push (test/security)Remote / CIBlocks invalid states locally before network transfer
Git Hooks: Automate Checks Before Commit and Push intercepts changes at three critical local stages before reaching the remote.

In my experience auditing infrastructure for SOC 2 compliance, organizations that rely exclusively on server-side checks often face "compliance fatigue." Developers push code, wait for CI to fail, fix it, and push again. By enforcing standards locally, you reduce noise in your audit logs and ensure that every commit landing in main already meets baseline security and formatting criteria. This is especially relevant when managing secrets management with HashiCorp Vault, where preventing accidental credential commits is far superior to rotating keys after a leak.

How Do You Configure Pre-Commit Hooks for Code Quality?

The pre-commit hook runs after you execute git commit but before the commit object is created. If the script exits with a non-zero status, the commit is aborted. This is your primary defense against style violations, unformatted code, and basic syntax errors.

Setting Up Husky for Node.js Projects

For JavaScript and TypeScript ecosystems, Husky is the industry standard. It simplifies hook installation and ensures hooks persist across clones.

# Install Husky as a dev dependency
npm install -D husky

# Initialize Husky (creates .husky/ directory and updates package.json)
npx husky init

# Add a lint-staged configuration to package.json
# This ensures only staged files are checked, improving performance
{
  "lint-staged": {
    "*.{js,ts,tsx}": ["eslint --fix", "prettier --write"],
    "*.css": ["stylelint --fix", "prettier --write"]
  }
}

A common mistake I see in Nepal’s growing tech scene is running linters against the entire codebase on every commit. Always scope checks to staged files using tools like lint-staged. On legacy monorepos, full-suite linting can take minutes; scoped checks take seconds. This distinction determines whether developers embrace or bypass your automation.

Using Pre-Commit Framework for Polyglot Repos

If your repository contains Python, Terraform, Go, or shell scripts alongside application code, the language-agnostic pre-commit framework is superior. Define a .pre-commit-config.yaml:

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.6.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: detect-private-key  # Critical for security compliance

  - repo: https://github.com/terraform-linters/tflint
    rev: v0.50.3
    hooks:
      - id: terraform_fmt
      - id: terraform_validate

This configuration enforces whitespace consistency and validates Terraform syntax before any infrastructure-as-code change is committed. When practicing infrastructure as code with Terraform, catching HCL formatting drift locally prevents state file inconsistencies during collaborative deployments.

Which Git Hook Enforces Commit Message Standards?

While pre-commit validates file content, the commit-msg hook validates the commit message itself. Consistent messaging is vital for automated changelogs, semantic versioning, and audit trails. In regulated environments, traceability between code changes and ticket IDs is often a mandatory control.

git commit -m "msg"commit-msg HookParse messageRegex / commitlintCheck ticket refCommit CreatedAbort + Error
The commit-msg hook parses message structure and rejects non-conforming commits before object creation.

I recommend commitlint paired with Conventional Commits. Create a commitlint.config.js:

module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [2, 'always', [
      'feat', 'fix', 'docs', 'style', 'refactor', 
      'perf', 'test', 'build', 'ci', 'chore', 'revert'
    ]],
    'subject-empty': [2, 'never'],
    'header-max-length': [2, 'always', 72]
  }
};

Add the hook via Husky: npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1". This enforces structured messages like feat(auth): add OAuth2 provider support. For teams in Nepal working with international clients, adhering to global conventions like this signals professionalism and makes your repository compatible with automated release tools like Semantic Release or Changesets.

When Should You Use Pre-Push Hooks for Security Scanning?

The pre-push hook executes after all commits are ready but before data transfers to the remote. This is your last line of defense. Use it for checks that are too slow for pre-commit or require broader context than individual files.

  • Full test suites: Running unit tests on every commit is fast; running integration tests is not. Reserve heavier test batches for pre-push.
  • Secret scanning: Tools like gitleaks or trufflehog scan history and diffs for credentials. While detect-private-key catches obvious patterns in pre-commit, deeper entropy analysis belongs here.
  • Branch protection validation: Prevent direct pushes to main or production branches locally before wasting bandwidth.
#!/bin/sh
# .husky/pre-push

# Block direct pushes to protected branches
protected_branches="^(main|master|production)$"
current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,')

if echo "$current_branch" | grep -qE "$protected_branches"; then
  echo "❌ Direct push to '$current_branch' is blocked."
  echo "   Please use a feature branch and pull request."
  exit 1
fi

# Run gitleaks on outgoing commits
echo "

Frequently Asked Questions

Git hooks are scripts triggered by repository events like commit or push. They automate linting, testing, and security scans locally before code enters version control, preventing bad commits from reaching shared branches.

Create an executable script named pre-commit inside .git/hooks. Ensure it has proper shebang lines and returns non-zero exit codes on failure to block the commit automatically when checks fail.

Yes, store hooks in your repository and configure core.hooksPath to point to that shared directory. This ensures every developer runs identical automation without manual setup or configuration drift between environments.

Pre-commit runs before creating a commit object, ideal for fast formatting checks. Pre-push executes after commits exist but before uploading, suitable for slower integration tests or validating branch policies against remote rules.

CaptainHook and GrumPHP are popular Composer packages for Laravel and PHP. They provide declarative JSON configuration, task runners, and plugin ecosystems specifically designed for PHP static analysis and testing workflows.

Poorly optimized hooks cause delays. Keep pre-commit tasks under two seconds by running only staged file checks. Move heavy test suites to pre-push or CI pipelines to maintain fast local feedback loops.

No, client-side hooks are easily bypassed with --no-verify flags. Always implement corresponding server-side receive hooks or CI pipeline gates to enforce mandatory checks regardless of local developer configurations.

Run the hook script manually from your terminal with verbose output enabled. Check stderr for specific error messages and verify environment variables match what git provides during actual hook execution context.

Yes, pre-commit hooks can auto-format code using tools like Prettier or PHP-CS-Fixer. Stage modified files programmatically within the hook so formatted changes are included in the pending commit automatically.

Hook scripts must have executable permissions set via chmod plus x. Without this flag, git silently ignores the hook regardless of correct naming or placement in the hooks directory.

Husky simplifies cross-platform setup and npm integration. Native hooks avoid Node dependencies and work universally. Choose based on team stack; both function identically once configured correctly in 2026.

Use the --no-verify flag with git commit or git push commands. Reserve this for emergency fixes only, as bypassing checks risks introducing unvalidated code into your repository history.

Yes, commit-msg and prepare-commit-msg hooks receive the message file path as an argument. Scripts can validate format, enforce ticket references, or append metadata before finalizing the commit.

The push operation aborts immediately and no data transfers to the remote. Developers must fix reported issues and retry, ensuring only validated commits reach shared repositories or deployment targets.

Client-side hooks alone are insufficient for compliance since developers control their local environment. Combine them with signed commits, protected branches, and mandatory CI status checks for auditable enforcement.