Skip to content
Notifications
Clear all

Husky vs lint-staged - which git hook strategy is better?

2 Posts
2 Users
0 Reactions
2 Views
(@data_pipeline_newbie_42_v2)
Estimable Member
Joined: 2 months ago
Posts: 106
Topic starter   [#20317]

Hey everyone! I'm trying to set up some basic pre-commit hooks for our team's Python data pipeline project (mostly Airflow DAGs and some dbt stuff). I keep seeing two main tools pop up: **Husky** and **lint-staged**. I've read docs and tutorials, but I'm still a bit confused about the real-world, practical difference.

From what I gather:
* **Husky** seems like the more general tool. You can run any script on any git hook.
* **lint-staged** is specifically for running linters/formatters on staged files.

My main goal is to automatically run black and ruff on our Python files before a commit. I tried setting up Husky directly and it felt... heavy? I had to write a script that figured out which files were staged. Then a teammate mentioned lint-staged is built for exactly that.

So my questions are:
* For a data engineering codebase, is one approach clearly better?
* Does using lint-staged *require* Husky as well, or can it run standalone?
* What happens with large files or slow linters? Does one handle that better?

I attached a screenshot of my first Husky attempt that failed because my script had a path error 😅. I'm leaning towards `lint-staged` now because it sounds simpler, but I don't want to miss out on Husky's flexibility if we need other hooks later (like pre-push integration tests).

What are you all using in your projects? Any gotchas I should watch out for?


null


   
Quote
(@ellaj8)
Trusted Member
Joined: 1 week ago
Posts: 67
 

Security lead at a 250-person SaaS shop. We enforce Python/JS linting and secret scanning on every commit; I run both tools in prod but for different jobs.

1. **Scope and Setup**: Husky is the generic git hook manager. You install it once, define hooks in `.husky/`, and it runs any shell script you want. Lint-staged is a single-purpose tool that must be called *from* a hook (like a Husky pre-commit). It handles the staging-area logic for you. You can't "use lint-staged without Husky" in a pure Node project; you need something to fire the git hook.
2. **Configuration Overhead**: For your goal (run black/ruff on staged Python files), lint-staged is 5 lines in `package.json`. A raw Husky script to do the same requires you to write the `git diff` and file filtering logic yourself, which is where your path error came from.
3. **Performance on Large Files**: Lint-staged defaults to running tasks in parallel and only on staged files, which is a major speed win. With slow linters, that's critical. In a 300k-line monorepo I managed, lint-staged kept pre-commit times under 2 seconds; a naive Husky script looping through all files took 12+.
4. **Ecosystem Lock-in**: Husky is language-agnostic. Lint-staged is Node-based (requires `npm` or `yarn`). If your team already runs `npm run` commands for tooling, this is fine. If your Python project has zero Node footprint, introducing it is a new dependency and mental tax.

My pick: use **both**. Husky to own the git hooks, lint-staged as the script it runs for formatting/linting. That's the standard pattern for a reason. For a pure Python shop allergic to Node, I'd write a single Husky script using `pre-commit` (the Python framework), but that's a different conversation.


Trust but verify – and audit


   
ReplyQuote