Skip to content

013-ADR: Git Hooks Manager

Architectural Decision Records (ADR) on implementing a Git Hooks Manager for Software Projects.

1. State

  • Author(s): Sentenz
  • Date: 2026-04-30
  • Status: Proposed

2. Context

A Git hooks manager automates tasks during the Git workflow, such as linting, formatting, and enforcing commit message conventions. Managing raw Git hooks as shell scripts lacks consistency, portability, and ease of onboarding. A dedicated Git hooks manager will standardize hook definitions, improve developer experience, and integrate seamlessly with our multi-language toolchain.

  1. Decision Drivers

    • Ease of Use

      The tool should provide a simple setup and configuration experience across all team members.

    • Cross-Platform Support

      The tool should work consistently on Linux, macOS, and Windows without requiring platform-specific workarounds.

    • Performance

      Hook execution should be fast and support parallel execution to minimize friction in the developer workflow.

    • Language Agnostic

      The tool should not be tied to a specific programming language or runtime, supporting our multi-language environment.

    • Scope Fit

      The tool should align with a polyglot repository baseline and avoid introducing language-specific onboarding requirements.

    • Configuration

      The configuration should be declarative, human-readable, and easy to maintain.

    • Integration

      The tool should integrate with existing linters, formatters, and CI/CD workflows.

    • Community and Ecosystem

      The tool should be actively maintained with good documentation and community support.

3. Decision

3.1. Lefthook

Lefthook is selected as the Git hooks manager due to its language-agnostic design, fast parallel execution, cross-platform support, and YAML-based declarative configuration that aligns with our existing toolchain conventions.

  1. Rationale

    • Ease of Use

      Lefthook uses a single lefthook.yml configuration file at the project root, making setup straightforward for all team members regardless of their primary programming language.

    • Cross-Platform Support

      Lefthook is distributed as a standalone binary with no runtime dependencies, ensuring consistent behavior on Linux, macOS, and Windows.

    • Performance

      Lefthook supports parallel execution of hook commands and runs only on staged files by default, minimizing execution time and developer disruption.

    • Language Agnostic

      Lefthook requires no specific runtime (e.g., Node.js or Python) and integrates with any tool or script available in the shell environment, making it suitable for our multi-language repository.

    • Scope Fit

      Lefthook fits into the repository scope by providing one baseline for all contributors without requiring language-specific setup (e.g., PHP/Composer)

    • Configuration

      The YAML-based lefthook.yml is declarative, version-controlled, and easy to read and update, consistent with other configuration files in the project.

    • Integration

      Lefthook integrates natively with CI/CD pipelines and can invoke any existing linting, formatting, or testing commands without additional wrappers.

    • Community and Ecosystem

      Lefthook is actively maintained by Evil Martians, has comprehensive documentation, and supports a growing ecosystem of integrations.

4. Considered

4.1. Git Hooks

Git Hooks are native shell scripts placed in the .git/hooks/ directory (or a custom path configured via core.hooksPath) that Git executes automatically at specific points in the workflow.

# githooks/pre-commit
#!/bin/sh
make run-linter-staged
# Configure custom hooks path
git config core.hooksPath githooks
  • Pros

    • No Dependencies

      Requires no additional tooling beyond Git itself, with zero external dependencies.

    • Native Support

      Built into Git and supported on all platforms without any installation step.

    • Full Control

      Complete control over hook scripts with no abstraction layer or framework constraints.

  • Cons

    • Ease of Use

      Hook scripts are not automatically shared with team members via version control, each developer must manually configure the hooks path or run a setup script.

    • Configuration

      No declarative configuration format, each hook is a separate shell script requiring individual maintenance and duplication of shared logic.

    • Performance

      No built-in support for parallel execution of multiple commands within a single hook.

    • Cross-Platform Support

      Shell scripts may behave differently on Windows without a Unix-compatible shell environment (e.g., Git Bash or WSL).

    • Integration

      Integrating multiple tools requires manual composition within shell scripts, increasing complexity and maintenance burden.

4.2. Husky

Husky is a widely used Git hooks manager for JavaScript and Node.js projects that relies on npm scripts and stores hook definitions in the .husky/ directory.

{
  "scripts": {
    "prepare": "husky install"
  }
}
# .husky/pre-commit
npx lint-staged
  • Pros

    • Ease of Use

      Simple setup for Node.js projects via npm install.

    • Adoption

      Widely adopted in the JavaScript ecosystem with extensive community resources.

    • Integration

      Tight integration with lint-staged for running checks only on staged files.

  • Cons

    • Runtime Dependency

      Requires Node.js and npm as a runtime dependency, which is not suitable for our multi-language environment.

    • Configuration

      Hook configuration is split across multiple shell scripts rather than a single declarative file.

    • Performance

      Does not support parallel execution of hook commands natively.

4.3. Lefthook

Lefthook is a fast, language-agnostic Git hooks manager distributed as a standalone binary that uses a lefthook.yml configuration file to define hooks.

# lefthook.yml
pre-commit:
  parallel: true
  commands:
    lint:
      run: make run-linter-staged
    spell:
      run: make run-spell-check

commit-msg:
  commands:
    lint:
      run: make run-linter-commit {1}
  • Pros

    • Language Agnostic

      No runtime dependencies beyond the binary itself, making it suitable for multi-language environments.

    • Performance

      Supports parallel execution of multiple commands within a single hook.

    • Configuration

      Single declarative YAML configuration file for all hooks.

    • Cross-Platform Support

      Cross-platform binaries available for Linux, macOS, and Windows.

    • Staged Files

      Supports filtering commands to run only on staged or changed files.

    • Community and Ecosystem

      Actively maintained with comprehensive documentation.

  • Cons

    • Binary Dependency

      Requires installing the lefthook binary on all development machines.

    • Adoption

      Less established than Husky in the JavaScript ecosystem.

4.4. CaptainHook

CaptainHook is a Git hooks manager focused on PHP projects, distributed as a Composer package with hook orchestration, local actions, and optional CI integration.

{
  "require-dev": {
    "captainhook/captainhook": "^5.0"
  }
}
vendor/bin/captainhook install
  • Pros

    • Workflow Features

      Provides rich hook orchestration, including local actions and conditional execution support.

    • Configuration

      Offers a dedicated JSON configuration with reusable actions and plugin extension points.

    • PHP Ecosystem Integration

      Integrates naturally with Composer-based PHP projects and tooling.

  • Cons

    • Runtime Dependency

      Requires a PHP and Composer runtime, which does not align with a language-agnostic baseline for all contributors.

    • Scope Fit

      Primary ecosystem focus is PHP projects, while this repository spans multiple languages and toolchains.

    • Onboarding

      Adds language-specific setup steps for contributors who do not otherwise need PHP in their development environment.

4.5. Pre-commit

Pre-commit is a framework for managing and maintaining multi-language pre-commit hooks, configured via a .pre-commit-config.yaml file and pulling hook definitions from remote repositories.

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
  • Pros

    • Ecosystem

      Rich ecosystem of ready-made hooks from the pre-commit community.

    • Language Agnostic

      Hook definitions that can invoke tools from various runtimes.

    • Environment Management

      Automatic virtual environment management for Python-based hooks.

  • Cons

    • Runtime Dependency

      Requires Python as a runtime dependency for the framework itself.

    • External Dependencies

      Hook definitions are fetched from remote repositories, introducing external dependencies and potential network latency.

    • Hook Coverage

      Configuration is limited to the pre-commit hook by default, other hooks require additional setup.

    • Performance

      Slower compared to Lefthook due to virtual environment management overhead.

5. Consequences

  • Positive

    • Consistency

      Standardized hook definitions in a single lefthook.yml file improve consistency and reduce onboarding time for new team members.

    • Performance

      Parallel execution of hook commands reduces wait time for developers during the Git workflow.

    • Portability

      No runtime dependency beyond the binary ensures hooks work identically across all supported operating systems and environments.

    • Maintainability

      Declarative YAML configuration is easy to read, update, and review in code reviews.

  • Negative

    • Binary Dependency

      All developers and environments must have the lefthook binary installed, requiring updates to onboarding documentation and setup scripts.

    • Migration Effort

      Existing raw shell-based Git hooks in the .git/hooks/ or githooks/ directory need to be migrated to Lefthook configuration, requiring testing to ensure equivalent behavior.

6. Implementation

  1. Install Lefthook

    Install the lefthook binary on all development machines and environments.

  2. Create Configuration

    Create a lefthook.yml configuration file at the project root, defining hooks for pre-commit, commit-msg, pre-push, post-checkout, and pre-rebase.

  3. Migrate Existing Hooks

    Migrate the existing hook logic from the .git/hooks/ or githooks/ shell scripts into the corresponding Lefthook commands.

  4. Register Hooks

    Run lefthook install to register the hooks in the local .git/hooks/ directory.

  5. Update Documentation

    Update the project README.md and onboarding documentation to include Lefthook setup instructions.

  6. Validate

    Validate hook execution of staged files to ensure consistent behavior across environments.

7. References