Where The Checks Belong
best-practices
Some commits should take two seconds. You fixed a typo, updated a config, cleaned up a comment. But the hook runs anyway full TypeScript check, the entire test suite and now you are sitting there waiting for something that has nothing to do with what you just changed.
I have timed it before. 47 seconds for three lines. By the time it finished, the next thing I was going to do was already gone.
Husky is supposed to be a convenience fast, local feedback before you push. Formatting, linting, commit message format. The kind of stuff that takes two seconds and saves you a dumb back-and-forth. But somewhere along the way it becomes a dumping ground, and by the time anyone notices, every commit costs half a minute nobody budgeted for.
How it gets there
It usually starts with a bad day.
A type error slips into main. Production breaks. Everybody scrambles. And in the next sprint someone quietly adds tsc to the pre-commit hook. Makes sense in the moment at least it won’t happen again. Then next month someone adds tests. “Just the fast ones.” Three months later the hook is running the whole pipeline and half the team has --no-verify aliased somewhere they don’t talk about.
Nobody announces that alias. Nobody documents it. It just quietly appears in a .zshrc after one too many broken trains of thought. You don’t blame them. You’ve probably done it too.
The checks still run nowhere. They’re just invisible now, which is somehow worse.
What actually belongs in a commit check
The real question isn’t how much to put in Husky. It’s whether what you’re running is actually about the commit you just made.
ESLint on staged files? Two seconds, scoped to what you changed. A type check on the files you touched? Fine. But a full tsc across the whole codebase, test suite, build validation that’s not your commit, that’s the entire project. That belongs in CI, running once, consistently, against the actual merge target.
Not on your laptop while you’re trying to keep a thought alive.
Yeah but CI is slow too
I hear this one a lot, and it’s fair. Push, wait 8 minutes, find a type error, push again. That loop is its own kind of painful.
But slow CI is a CI problem. You fix it by making CI faster parallelise it, cache it, scope it. You don’t fix it by making every local commit absorb the cost instead. The time doesn’t disappear. It just gets distributed across every engineer, every day, in 40-second chunks nobody counts.
And I know “fix your CI” isn’t always actionable. A lot of teams don’t own CI, it lives with a platform team and the queue is somebody else’s problem. So you compensate with Husky because it’s the only lever you can reach. That’s understandable. It’s still treating the symptom though.
Where do we draw the line
| Task | Run in Husky | Run in CI |
|---|---|---|
| Prettier | ✅ | Optional |
| ESLint (staged files) | ✅ | ✅ |
| Commit message validation | ✅ | Optional |
| TypeScript (staged files) | Optional | ✅ |
| TypeScript (full) | ❌ | ✅ |
| Unit/integration tests | ❌ | ✅ |
| Build validation | ❌ | ✅ |
Keep commits fast. Keep the thoughts alive. The checks will still run just somewhere you don’t have to sit and wait for them.