Block bad pushes with a git hook
Dead code doesn’t get deleted because nobody notices it’s there. A hook that fails the push when it finds some means it gets caught before it ever reaches the repo.
Unused exports, orphaned files, dependencies nobody imports anymore. They pile up quietly in every JavaScript project, because deleting code feels riskier than leaving it. This wires a check into the moment you push, so the question gets asked automatically instead of never.
What you’ll need
- A JavaScript or TypeScript project with a git repo
- Node.js installed
- About 15 minutes
See what Knip finds, before automating anything
Knip is the current tool for this. The older ones, depcheck and ts-prune, were both archived, Knip replaced both by doing dependency, export, and unused-file analysis together.
Run it once, with no setup, to see your project’s actual state:
npx knip
It’ll list unused files, unused exports, and unused dependencies. Read through it before you automate anything, some flagged items are false positives, entry points Knip doesn’t know are entry points, and those need a config file to exclude. Don’t wire a hook to a tool you haven’t looked at first.
Add a config if you need one
If Knip flagged something that’s actually used, like a file only referenced by your build tooling, tell it so. Create knip.json in the project root:
{
"entry": ["src/index.ts"],
"project": ["src/**/*.ts"]
}
Adjust the paths to match your project. Re-run npx knip and confirm the false positives are gone.
Install Husky
Husky manages git hooks so they live in the repo and travel with it, instead of sitting only on your machine.
npm install --save-dev husky
npx husky init
That creates a .husky/ folder and adds a prepare script to package.json, so the hook installs itself for anyone who clones the repo and runs npm install.
Wire Knip to the push
husky init creates a sample .husky/pre-commit. For this, use pre-push instead, since a full dead-code scan on every single commit is slower than most people want mid-flow, but the moment before code leaves your machine is exactly the right time to check.
Create .husky/pre-push:
npx knip
That’s the whole hook. Knip exits with a non-zero status when it finds issues, and a non-zero exit from a git hook stops the git operation. No extra plumbing.
Watch it actually block something
Prove it before you trust it. Add an obviously unused export somewhere in your source, an unimported function, a file nothing links to. Commit it, then push:
git add .
git commit -m "test: add unused export"
git push
The push should fail, with Knip’s output telling you exactly what it found. Delete the test code, push again, it goes through clean.
What this doesn’t cover
It’s a speed bump, not a wall. git push --no-verify skips every hook. That’s intentional, in an emergency you shouldn’t be blocked by a linter, but it means this catches accidents, not someone actively working around it.
It only runs on your machine. If more than one person works on the repo, each person needs to npm install for the hook to install locally. A CI check running the same npx knip command is the version nobody can skip.
Existing debt doesn’t get fixed by this. The hook stops new dead code from arriving. Cleaning out what’s already there is a separate, one-time pass.
The short version
npx kniponce, read the output, add aknip.jsonfor false positivesnpm install --save-dev husky && npx husky init.husky/pre-pushcontains one line:npx knip- Push something with an unused export, watch it get blocked
--no-verifybypasses it on purpose; a CI check is the version that can’t be skipped
Sources
- Knip documentation — the tool itself, setup and configuration
- Husky documentation — git hooks that live in the repo
Written August 2026.
Prints to PDF from your browser — colours and all.