Skip to content

Use Lefthook for repository-managed Git hooks

Status

Accepted

Context

EOS uses pnpm workspaces and Turborepo to coordinate checks across applications, libraries, and versioned plugin packages. The repository also uses Nix to provide its development toolchain. Linting, formatting, typechecking, testing, and documentation validation already have repository-level commands and authoritative CI jobs.

Git hooks can provide earlier feedback, but they introduce a separate local execution boundary. A hook system must install reliably for contributors, select only relevant staged files, handle paths across the monorepo, and avoid duplicating the task graph owned by Turborepo. It should also accommodate non-Node tooling if EOS becomes more polyglot.

Running the complete workspace check before every commit would increase commit latency and repeat work already performed by CI. Limiting hooks to fast checks requires file filtering, conditional execution, and safe parallelism. These capabilities can be supplied by a hook manager, composed from multiple Node.js tools, or implemented in repository-owned scripts.

Decision Drivers

  • Fast feedback based on the files staged for the current commit.
  • One repository-level configuration for all pnpm workspaces.
  • Compatibility with the existing pnpm, Turborepo, and Nix setup.
  • Conditional execution by path and file type without custom routing scripts.
  • Parallel execution of independent read-only checks.
  • Support for Git hook types beyond pre-commit if they are needed later.
  • A language-neutral mechanism that can invoke future non-Node tooling.
  • Minimal overlap with Turborepo’s package graph and CI’s authoritative checks.
  • Low configuration and dependency overhead.

Considered Options

Lefthook

Lefthook is a standalone Git hook manager configured from a repository-level YAML file. It provides staged-file templates, glob and path filtering, per-command working directories, parallel jobs, and multiple Git hook types. It can be installed as a pnpm development dependency while remaining neutral about the languages and commands it executes.

Lefthook can perform the required staged-file routing without an additional tool such as lint-staged. Its configuration can invoke the repository’s existing locally installed tools and can add jobs for other ecosystems without changing hook managers.

Husky with lint-staged

Husky provides a lightweight connection between Git hooks and repository-owned shell scripts. lint-staged adds staged-file filtering, command concurrency, and handling for partially staged files.

Together they meet the functional requirements and are familiar within the Node.js ecosystem. They require two dependencies and two configuration layers, with hook behavior divided between shell files and lint-staged configuration. Husky alone would require EOS to implement the staged-file orchestration that lint-staged or Lefthook already provides.

pre-commit

pre-commit is a multi-language hook framework that can install tools into isolated environments and run them against matching files. It is well suited to repositories that want the hook system to provision and version toolchains independently of the repository’s primary development environment.

EOS already uses Nix for its development toolchain and pnpm for Node.js dependencies. Adding pre-commit would introduce a Python-managed installation, cache, and dependency lifecycle alongside those existing mechanisms. That isolation is not currently required for the repository’s locally installed checks.

simple-git-hooks

simple-git-hooks installs commands from package configuration with little additional machinery. It would keep the dependency and installation surface small.

It does not provide the staged-file filtering, monorepo routing, or job orchestration required by this decision. EOS would need to implement and maintain those capabilities in scripts.

Native Git hooks maintained by EOS

EOS could maintain hook scripts directly and configure Git to use them during repository setup. This would avoid a hook-manager dependency and allow complete control over execution.

The repository would then own cross-platform installation, updates, file selection, quoting, concurrency, and contributor opt-out behavior. Those responsibilities are not specific to EOS and would add maintenance without changing the checks being run.

Decision Outcome

EOS will use Lefthook to manage repository-provided Git hooks.

Lefthook will be installed as a development dependency at the pnpm workspace root, and its required installation script will be explicitly allowed by the workspace’s dependency build policy. Hook behavior will be declared in a root lefthook.yml file.

The initial pre-commit hook will run fast, read-only formatting and linting checks only for relevant staged files. Independent checks may run in parallel. It will not run the complete pnpm check command or duplicate Turborepo’s workspace dependency graph. Builds, full typechecking, tests, and release validation remain authoritative in CI. A future pre-push hook may invoke affected-package Turbo tasks if commit-time checks prove insufficient, but that is outside this decision.

Consequences

Positive

  • Contributors receive formatting and linting feedback before creating a commit.
  • Staged-file selection, path filtering, and parallel execution are expressed in one repository-level configuration.
  • EOS does not need lint-staged or custom shell orchestration for the initial hook set.
  • The hook configuration can cover multiple workspaces and future languages without changing managers.
  • Turborepo and CI retain clear ownership of package-level and complete repository verification.

Negative

  • Lefthook adds a development dependency and runs an installation script that must be permitted by pnpm’s dependency build policy.
  • Contributors must reinstall or repair hooks if their local Git configuration is changed or the installation step is skipped.
  • Commands invoked by hooks still require their underlying tools to be available; Lefthook does not provide isolated tool environments.
  • Contributors unfamiliar with Lefthook must learn its configuration and local opt-out mechanisms.

Neutral

  • Git hooks remain bypassable and therefore provide feedback rather than the final enforcement boundary; CI continues to enforce repository policy.
  • Lefthook manages command execution but does not replace pnpm, Nix, or Turborepo.
  • Commits made from terminals inside the Nix development environment have the required Node.js and pnpm commands. Hook behavior from Git GUI clients or shells outside that environment must be validated during implementation and may require a documented environment bootstrap.
  • The exact staged-file globs and commands will evolve with the repository’s supported file types while preserving the decision boundary described here.