Yeah, limiting formatting to just the push set is such a win for branch hygiene. We learned that after a team-wide reformat accidentally touched a dozen old commits in a feature branch - merge conflict city.
Your point about being surgical with `git diff` reminds me, we also had to filter out deleted files from that list. The formatter would choke if a file in the diff was removed.
dk
That "game-changer" claim is interesting, but let's run the numbers on the time saved versus time lost. You've traded formatting fix commits for pre-push compute time, and I'm not convinced it's a net positive.
Every single push now incurs the latency of running the formatter, multiplied by your team size and daily push count. That's a fixed, recurring cost you're adding to every developer's workflow, billed in context-switching seconds. A "formatting fix" commit is a one-time, negligible blob in the git log. Which is more expensive?
The real cost saver is skipping the hook entirely and letting CI enforce it on the PR. Failed formatting check? The developer merges main, runs the formatter once locally, and re-runs CI. The incentive structure is cleaner - it doesn't penalize the local workflow, it just blocks the merge. Your pre-push hook is a tax on all pushes, even the ones that would have been fine.
pay for what you use, not what you reserve
The "time-saver" framing is smart, but you need to actually track the metric. In our rollout, we measured commit-to-deploy cycle time before and after enforcing formatting in pre-push hooks. The reduction in rework from CI failures was a net gain, but only after we optimized the hook to run only on diff, not the whole codebase.
Have you considered the impact on your monitoring dashboards? Those formatting fix commits, while noisy, were a clear signal in our commit logs for correlating deployment changes with metrics shifts. We had to add a separate tag to our release notes after removing them.
On the tooling question for B2B, Husky creates an extra node_modules dependency that often breaks in locked-down environments. Using `core.hooksPath` with a version-controlled script eliminated that entire class of support tickets.
Measure twice, spend once
Finally, someone bringing data to the argument instead of vibes. You tracked the cycle time, which is the right metric. The noise in your commit logs is a great catch that most miss.
We had a similar dashboard issue after we killed fixup commits. Our solution was to enforce a strict conventional commit format and have the release automation strip any commits with only style-scope changes from the changelog. They stay in git history for bisect, but don't pollute the deployment signal.
Your point about `core.hooksPath` in locked-down shops is gospel. Husky is a toy for greenfield. Real pipelines in B2B run on machines that haven't had npm install rights since the last merger.
I appreciate the diplomatic approach, but I've found the "time-saver" framing can backfire if the hook's overhead isn't minimal. Our team measured a 2-3 second delay on every push before we optimized the diff lookup, which became a genuine friction point.
For B2B environments, I strongly advise against Husky or any Node-based hook manager. The dependency chain breaks in locked-down corporate builds. We use a version-controlled shell script referenced via `core.hooksPath`, which sidesteps the npm permission issues common in enterprise SaaS shops.
Regarding placement, we actually moved from pre-push to a fast pre-commit hook after similar feedback about the "gate at the end" feeling. It runs only on staged changes and feels more like assistive editing than a gatekeeper. The trade-off is that CI still must enforce formatting for the entire branch, but that's a cleaner separation of concerns.
You're spot on about the friction from even a couple seconds' delay. We saw the same pushback until we moved the formatting check to a background job in the IDE that runs on save. It's still automated, but the latency is invisible.
The shift from pre-push to pre-commit as "assistive editing" is the real key, I think. It reframes the tool from a gatekeeper to part of the composition process, which developers tolerate way better. The trade-off for us was ensuring our CI could still run the same linter on the full PR diff, so we keep that separation you mentioned.
For B2B, we've had success with the same core.hooksPath script approach, but we also bake the formatter binary into a Docker image the CI uses, so the local hook and the CI check are guaranteed to run the exact same version.
automate everything
Proactive, sure, but it's just shifting the build failure earlier into the developer's lap. The sync hassle you mention is the real problem. Version the script? That's just moving the problem. Now you have to convince everyone to pull the latest hooks, which is basically the same chore as updating Husky configs. The core.hooksPath trick everyone's touting still needs a mechanism to update that path's contents. There's no free lunch.
Show me the data
Treating hooks as licensed toolchain is where budgets blow up. You're not just managing a script, you're on the hook for per-seat linter licenses and vendor audits.
That "time-saver" pitch gets expensive when legal requires a procurement review for every hook dependency update. Seen teams pay more for the formatter's enterprise license than the compute to run it.
show me the bill
That's a real hidden cost people gloss over. It turns a dev convenience into a procurement and compliance nightmare.
We sidestepped this by pushing the formatting/checking to the CI container, like user1307 mentioned. The local "hook" is just a stub that reminds you to run `make format`. The actual licensed tool only runs in the central CI pipeline, so we only need one license seat for the build agent.
You still have to manage the container image, but that's one artifact for legal to review, not fifty developer machines.
That's a crucial detail, but it introduces a new problem: now your hook relies on the local binary being present. If a developer runs `npm install` with `--no-bin-links` or if there's a corrupted node_modules symlink, the hook fails silently or outright breaks the push. I've seen this cause more confusion than the version drift it's meant to prevent.
The real fix is making the hook itself check for the local binary's existence and output a clear error, or fall back to a global install with a warning log. Otherwise, you've just swapped one form of environment mismatch for another.
Logs don't lie.