evilmartians/lefthook
Fast and powerful Git hooks manager for any type of projects.
Healthy across the board
weakest axisPermissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 3d ago
- ✓19 active contributors
- ✓MIT licensed
Show all 6 evidence items →Show less
- ✓CI configured
- ✓Tests present
- ⚠Concentrated ownership — top contributor handles 63% of recent commits
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/evilmartians/lefthook)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/evilmartians/lefthook on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: evilmartians/lefthook
Generated by RepoPilot · 2026-05-09 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/evilmartians/lefthook shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across the board
- Last commit 3d ago
- 19 active contributors
- MIT licensed
- CI configured
- Tests present
- ⚠ Concentrated ownership — top contributor handles 63% of recent commits
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live evilmartians/lefthook
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/evilmartians/lefthook.
What it runs against: a local clone of evilmartians/lefthook — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in evilmartians/lefthook | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 33 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of evilmartians/lefthook. If you don't
# have one yet, run these first:
#
# git clone https://github.com/evilmartians/lefthook.git
# cd lefthook
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of evilmartians/lefthook and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "evilmartians/lefthook(\\.git)?\\b" \\
&& ok "origin remote is evilmartians/lefthook" \\
|| miss "origin remote is not evilmartians/lefthook (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "cmd/lefthook.go" \\
&& ok "cmd/lefthook.go" \\
|| miss "missing critical file: cmd/lefthook.go"
test -f "cmd/run.go" \\
&& ok "cmd/run.go" \\
|| miss "missing critical file: cmd/run.go"
test -f ".lefthook.yml" \\
&& ok ".lefthook.yml" \\
|| miss "missing critical file: .lefthook.yml"
test -f "go.mod" \\
&& ok "go.mod" \\
|| miss "missing critical file: go.mod"
test -f "Makefile" \\
&& ok "Makefile" \\
|| miss "missing critical file: Makefile"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 33 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~3d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/evilmartians/lefthook"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Lefthook is a Go-based Git hooks manager that installs, configures, and orchestrates pre-commit, pre-push, and other Git hooks across projects written in Node.js, Ruby, Python, and other languages. It enables parallel execution of linting, testing, and validation tasks on staged files with zero runtime dependencies—a single binary that works everywhere. Monolithic Go application: cmd/ directory contains CLI commands (install.go, run.go, dump.go, validate.go, etc.), core logic in package directories (inferred from imports), configuration parsing via koanf with YAML/TOML/JSON support, and git integration for file filtering. Docs live in docs/configuration/ with detailed markdown for each feature (Commands.md, Hook.md, Scripts.md). Single .lefthook.yml config file at project root.
👥Who it's for
Frontend and backend engineers at organizations using mixed-language monorepos or polyglot projects who need a unified, fast way to enforce code quality checks (linting, formatting, tests) before commits and pushes without installing language-specific hook managers.
🌱Maturity & risk
Production-ready. The repo shows v2.1.6 as stable release, includes comprehensive GitHub Actions CI/CD (test.yml, lint.yml, release.yml, codeql.yml), high test coverage tracked in codecov.yml, and active maintenance with dependabot configured. Clear sign of maturity: multi-language package distribution (Go, NPM, Ruby, Python, Homebrew, apt, winget).
Low risk. Minimal direct dependencies (mostly Go stdlib + utility libraries like lipgloss, urfave/cli, yaml parsers, no heavy frameworks). Single Go binary eliminates runtime dependency hell. Main risk: core functionality depends heavily on file globbing (gobwas/glob) and YAML parsing (goccy/go-yaml)—bugs in those dependencies could affect all users. Maintainer concentration around Evil Martians organization is typical for OSS.
Active areas of work
Active development: Go 1.26 toolchain requirement added (go.mod specifies 1.26), release automation via goreleaser.yml and GitHub Actions, ongoing dependency updates via dependabot.yml, and documentation expansion (book.toml suggests generated docs site via mdbook). Recent focus on validation (cmd/validate.go) and self-update mechanisms (cmd/self_update.go).
🚀Get running
Clone and build: git clone https://github.com/evilmartians/lefthook.git && cd lefthook && make build (Makefile present). Or install binary directly: go install github.com/evilmartians/lefthook/v2@v2.1.6. Then in your project: create .lefthook.yml, run lefthook install, and commit hooks execute automatically.
Daily commands:
Development: make build outputs binary (check Makefile). Run: ./lefthook install in a git repo to install hooks, then git operations trigger them. For testing hooks: lefthook run <hook-name> (e.g., lefthook run pre-commit). Run tests: make test (inferred from workflows/test.yml).
🗺️Map of the codebase
cmd/lefthook.go— Main entry point; defines the CLI application structure and command routing—understand this first to navigate the codebasecmd/run.go— Implements the core hook execution logic; handles git hooks triggering and the orchestration of commands.lefthook.yml— Default configuration file showing the schema and configuration format that all users and developers must understandgo.mod— Declares all dependencies including YAML parsing, glob matching, and PTY libraries; required reading for dependency managementMakefile— Build and development workflow automation; essential for understanding how to build, test, and release the projectdocs/configuration.md— Comprehensive configuration documentation; foundation for understanding all available features and options.github/workflows/test.yml— CI/CD test pipeline definition; shows testing strategy and which platforms are supported
🧩Components & responsibilities
- CLI Entry Points (cmd/lefthook.go, cmd/run.go, cmd/install.go) (Go CLI framework, charmbracelet/lipgloss) — Parse user input, route to appropriate command handler, and report results
- Failure mode: Invalid flag syntax, missing required arguments, or hook not found → user receives error message
- Configuration Parser (koanf, goccy/go-yaml) (koanf/v2, goccy/go-yaml, fs provider) — Load .lefthook.yml, merge extends, fetch remotes, and validate schema
- Failure mode: Malformed YAML, circular extends, network failure on remote fetch → config validation error with location info
- Hook Runner (cmd/run.go logic) (Go goroutines, bmatcuk/doublestar, creack/pty) — Orchestrate hook execution, resolve file globs, apply filters, and aggregate results
- Failure mode: Command timeout, glob expansion failure, or non-zero exit code → hook blocked or allowed based on config
- Command Executor — Execute individual hook commands with
🛠️How to make changes
Add a new CLI command
- Create a new file in cmd/ directory following the naming pattern cmd/mycommand.go (
cmd/mycommand.go) - Implement the command with a Run() method following the pattern in cmd/run.go or cmd/install.go (
cmd/mycommand.go) - Register the command in cmd/commands.go by adding it to the Commands struct and initialization (
cmd/commands.go) - Update the CLI app setup in cmd/lefthook.go to wire the command into the CLI (
cmd/lefthook.go)
Add a new configuration option
- Add the field to the appropriate configuration struct (usually in config package, inferred from schema) (
go.mod) - Document the new option in the configuration documentation (
docs/configuration/mynewopt.md) - Update the main configuration reference (
docs/configuration.md) - Add example usage to .lefthook.yml if it's a commonly used feature (
.lefthook.yml)
Add support for a new project type or language
- Extend the glob matching logic if needed (leverages bmatcuk/doublestar library) (
go.mod) - Add example configuration in the docs/examples directory (
docs/examples/myframework.md) - Update README.md to mention the newly supported project type (
README.md) - Add integration tests in the test workflow if platform-specific (
.github/workflows/test.yml)
🔧Why these technologies
- Go 1.26 — Compiled binary enables single-dependency-free distribution; fast execution for git hook latency-sensitive operations
- koanf (config parsing) — Multi-format config support (YAML, TOML, JSON); flexible and extensible configuration merging for extends & remotes
- goccy/go-yaml — Robust YAML parsing with Go-native performance; handles complex configuration schemas
- bmatcuk/doublestar (glob matching) — Supports advanced glob patterns for flexible file filtering; ** and * wildcard support for cross-platform projects
- creack/pty — Pseudo-terminal support for interactive hooks; preserves color output and TTY behavior in piped commands
- charmbracelet/lipgloss — Terminal styling and colors for user-friendly CLI output
⚖️Trade-offs already made
-
Single compiled Go binary vs modular interpreter-based solution
- Why: Users wanted zero dependencies and portability; hooks run on every commit so speed is critical
- Consequence: No dynamic language support; must handle all use cases in Go; larger single binary vs many small modules
-
YAML-first configuration with support for TOML/JSON via koanf
- Why: YAML is human-readable and familiar to most developers; koanf allows graceful fallback
- Consequence: YAML indentation errors can be cryptic; configuration size grows with complexity; no schema validation via JSON Schema in runtime
-
Parallel job execution via goroutines
- Why: Minimize total hook execution time for developers; maximize parallelism on multi-core machines
- Consequence: Added complexity in result aggregation and error reporting; requires careful race-condition prevention
-
Remote configuration fetch (git clone of external repos)
- Why: Enable shared hook configurations across teams and projects; DRY principle for large organizations
- Consequence: Network dependency; additional git operations add latency; potential security surface (arbitrary git URLs)
🚫Non-goals (don't propose these)
- Does not handle authentication or secret management; lefthook runs as the current user with their git credentials
- Not a project scaffolding tool; does not generate new repositories or boilerplate code
- Does not replace CI/CD systems; is a local pre-commit/pre-push enforcement tool only
- Not a Git replacement or wrapper; only interfaces with existing git hooks
- Does not provide GUI or web interface; CLI-first design
🪤Traps & gotchas
Git hooks directory permissions: ensure .git/hooks/ is writable after install. Config path precedence: lefthook.yml takes priority; other formats (TOML, JSON) must be explicitly named. Glob behavior: patterns use doublestar syntax, not shell glob—test with lefthook dump to verify matching. Parallel execution: jobs with shared resources (same linter, same files) may race; order matters. File variables ({staged_files}, {all_files}) require Git commands to work; won't expand offline. Self-update: only works if lefthook binary is in PATH or installed via package manager; Go install users must update manually.
🏗️Architecture
💡Concepts to learn
- Git Hooks & Local Git Workflows — Lefthook's entire purpose is automating Git hooks; understanding pre-commit, pre-push, commit-msg phases is fundamental to using and extending it
- Glob & Pattern Matching (doublestar/brace expansion) — File filtering in Lefthook relies on glob patterns (.go, **/.rb, etc.); bugs in pattern matching directly break job execution
- Pseudo-Terminal (PTY) Allocation — Lefthook uses creack/pty to allocate PTYs for hook processes, enabling interactive prompts and colored output; understanding TTY allocation helps debug hook failures
- Concurrent Process Orchestration (Goroutines) — Lefthook's parallel: true feature spawns goroutines for concurrent job execution; understanding goroutine coordination and error handling is critical for adding parallel-aware features
- Configuration Schema & Unmarshaling — Lefthook uses koanf + mapstructure to parse YAML/TOML/JSON into strongly-typed structs; modifying config schema requires understanding struct tags and validation
- JSON Schema Validation (invopop/jsonschema) — Lefthook generates and validates config against JSON schemas; used in cmd/validate.go to catch user config errors early
- Cross-Platform Binary Distribution — Lefthook uses goreleaser.yml to build and distribute binaries across Linux, macOS, Windows, and package managers (npm, gem, pip); understanding this pipeline is key to making changes that affect releases
🔗Related repos
pre-commit/pre-commit— Python-based Git hooks framework with similar goal; Lefthook differentiates via language-agnostic binary and parallel executionhusky/husky— NPM-focused Git hooks manager; Lefthook is the polyglot alternative that doesn't require Node.jslint-staged/lint-staged— Runs linters on staged files; often paired with Lefthook for file filtering and job orchestrationevilmartians/overmind— Companion tool by Evil Martians for process management; users often combine with Lefthook for development automationevilmartians/golangci-lint— Go linter aggregator frequently used as a Lefthook job; shared ecosystem and common pairing
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive integration tests for hook execution with parallel jobs
The repo highlights 'parallel execution' as a key feature in the README, but there's no dedicated test coverage visible for the jobs configuration parameter. The internal/lefthook package likely needs tests that verify correct parallel execution, job isolation, and failure handling across multiple concurrent tasks.
- [ ] Review existing tests in internal/lefthook to identify coverage gaps for job parallelization
- [ ] Create integration tests in internal/lefthook/test that verify parallel job execution correctness
- [ ] Add test cases for edge cases: job failure propagation, concurrent file access, and stdout/stderr ordering
- [ ] Update docs/configuration/jobs.md with examples of test scenarios if needed
Implement schema validation tests for all configuration file formats (YAML, JSON, TOML)
The repo supports multiple config formats (YAML, JSON, TOML) via koanf parsers and has a JSON schema generator (invopop/jsonschema), but there's no visible test suite validating that invalid configs are rejected consistently across all formats. This is critical for user experience since config errors should fail early.
- [ ] Examine internal/config parsing logic to understand current validation
- [ ] Create test fixtures in a new test/fixtures/invalid-configs directory with malformed configs in each format
- [ ] Add unit tests that verify validation errors are raised for: invalid hook names, circular extends, missing required fields
- [ ] Verify the jsonschema generation in cmd/dump.go produces accurate schema for all supported config options
Add end-to-end tests for the extends and configs configuration merging behavior
The repo has complex configuration composition features (docs/configuration/extends.md and docs/configuration/configs.md) but no visible E2E tests verifying inheritance chains, config file precedence, and override behavior across multiple levels. This is high-risk for regressions.
- [ ] Create test scenarios in test/fixtures that demonstrate: simple extends, multi-level extends, configs array merging, and conflicting overrides
- [ ] Add integration tests that run lefthook with these complex configs to verify correct hook execution
- [ ] Test edge cases: circular extends detection, missing extended files, and precedence when both extends and configs are used
- [ ] Document the expected behavior in docs/configuration/README.md if precedence rules aren't clearly explained
🌿Good first issues
- Add integration tests for cmd/validate.go to ensure config validation catches schema violations; currently no dedicated test file visible in structure.
- Expand docs/configuration/args.md with concrete examples showing variable substitution for {staged_files}, {all_files}, and custom file lists across different Git states.
- Add Windows path handling tests in cmd/install.go to verify hook script generation works on Windows (currently unclear if PowerShell/cmd.exe shims are tested).
⭐Top contributors
Click to expand
Top contributors
- @mrexox — 63 commits
- @scop — 18 commits
- @iloveitaly — 2 commits
- @joevin-slq-docto — 2 commits
- @lawrence3699 — 1 commits
📝Recent commits
Click to expand
Recent commits
76aa843— fix: linter, sacrifice optimization for readability (mrexox)9d53c36— fix: separate fallback push branch from pathspecs (#1396) (lawrence3699)679ce27— 2.1.6: fixes for Windows and AI tools execution (mrexox)04da006— fix(windows): normalize lefthook path for sh script (#1383) (AndrewKahr)eb3e70d— fix: normalizerootto always include trailing slash before path replacement (#1381) (Copilot)f90f3f5— fix: skip pty allocation when stdout is not a terminal (#1393) (technicalpickles)1481e9d— docs: upgrade docmd (#1391) (mrexox)de9597a— fix: log full scoped name for skipped jobs (#1291) (scop)bf73ea2— fix(packaging): do not pipe stdout and stderr (#1382) (mrexox)4cec579— 2.1.5: prevent overwriting global hooks and fix pre-push for sha256 repos (mrexox)
🔒Security observations
Lefthook has a moderate security posture with deliberate design choices that accept execution of arbitrary commands. The main security concern is the inherent execution model which requires careful configuration management. Dependencies appear reasonably maintained, though there is redundancy in parsing libraries. No critical vulnerabilities were identified in the visible codebase structure, but the incomplete go.mod information and use of an unreleased Go version warrant attention. The project demonstrates security awareness through clear documentation of its threat model in SECURITY.md.
- Medium · Execution of Arbitrary Commands by Design —
Core functionality (cmd/run.go, configuration parsing). Lefthook is designed to execute arbitrary commands and scripts from its configuration file. While this is intentional behavior as documented in SECURITY.md, it creates a significant attack surface if configuration files are not properly controlled or validated. Malicious or compromised configuration files could lead to arbitrary code execution. Fix: Implement strict configuration file validation, restrict configuration file permissions, and provide clear documentation on securing configuration files. Consider implementing configuration signing/verification mechanisms for sensitive environments. - Medium · Outdated Go Toolchain Version —
go.mod - toolchain declaration. The project specifies Go 1.26 which is a future release version. This may indicate development against unreleased versions or potential version management issues. Using non-standard/unreleased toolchain versions can introduce instability and unpredictable behavior. Fix: Use stable, released Go versions. Pin to the latest stable release (1.23.x or later) and test thoroughly before updating. - Low · Incomplete Dependency Information —
go.mod dependencies section. The provided go.mod content appears truncated (ends at 'mailru/easyjson v0.7.7 //'). This makes it impossible to perform a complete security audit of all dependencies. Fix: Provide complete dependency list. Regularly run 'go mod tidy' and use 'go list -m all' to verify all dependencies. Consider using 'nancy' or 'vulncheck' to scan for known vulnerabilities. - Low · Dependency on Multiple YAML/JSON Parsers —
go.mod - dependencies: github.com/goccy/go-yaml, github.com/knadh/koanf, github.com/tidwall/jsonc, github.com/invopop/jsonschema, github.com/kaptinlin/jsonschema. The project includes multiple YAML and JSON parsing libraries (goccy/go-yaml, koanf, tidwall/jsonc, invopop/jsonschema, kaptinlin/jsonschema). This increases the attack surface and maintenance burden. Each parser could have latent vulnerabilities. Fix: Consolidate on a single, well-maintained YAML/JSON parsing library. Remove redundant dependencies. Document why multiple parsers are necessary if consolidation isn't possible. - Low · PTY Interaction Library Dependency —
go.mod - github.com/creack/pty v1.1.24. The creack/pty library (v1.1.24) is used for terminal interaction. While generally safe, terminal handling code can be sensitive to privilege escalation or terminal-based injection attacks. Fix: Keep this dependency updated. Review usage patterns for any unsanitized user input being passed to PTY operations. Consider sandboxing subprocess execution.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.