Lesson 21 of 30
Git already runs your scripts at the right moments — the trick is making them travel with the repo instead of living only on your machine.
14 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
A hook is just a script that Git runs automatically at a specific point in its workflow — before a commit is created, before a push leaves your machine, after a checkout, and a dozen other moments. Nothing magic: Git looks in .git/hooks/, and if it finds an executable file named exactly pre-commit, pre-push, commit-msg, and so on, it runs it and pays attention to the exit code. Exit 0 and Git proceeds. Exit non-zero and Git stops — the commit doesn't happen, the push doesn't go out.
The catch is right there in the path: .git/ is never tracked by Git itself. Every clone of your repo gets a fresh, empty .git/hooks/ full of .sample files that do nothing. If you hand-write a pre-commit hook on your laptop, it enforces nothing for anyone else — it's a personal habit, not a team guarantee. That's the entire reason tools like Husky exist: they don't invent a new hook mechanism, they just solve the distribution problem. Husky writes its hook scripts into a folder inside the repo (commonly .husky/), which git does track, and then points Git's core.hooksPath config at that folder instead of .git/hooks/. Now the hook ships with git clone like any other file, and a postinstall script wires it up automatically the moment someone runs pnpm install.
Here's a raw hook first, no tooling, to see exactly what Git is doing:
$ cat .git/hooks/pre-commit
#!/bin/sh
pnpm exec eslint . --max-warnings=0
$ chmod +x .git/hooks/pre-commit
$ git commit -m "add feature"
/repo/components/Card.tsx
12:7 error 'unused' is defined but never used no-unused-vars
1 problem (1 error, 0 warnings)
The commit never happens — ESLint exited non-zero, so the hook exited non-zero, so Git refused to create the commit object. That's the whole mechanism.
Now the same thing via Husky, which is what actually ships in a team repo:
$ pnpm add -D husky
$ pnpm exec husky init
$ cat .husky/pre-commit
pnpm exec eslint . --max-warnings=0
pnpm exec tsc --noEmit
.husky/pre-commit is a tracked file — it's in git status, it's in the diff, it goes out in the next git push just like a .ts file would. Anyone who clones the repo and runs pnpm install gets the exact same gate on their machine, with zero manual setup. A raw .git/hooks/pre-commit can't offer that; it's local by construction.
.git/hooks/*.sample files aren't active. Git ships every repo with sample hooks for reference — pre-commit.sample, pre-push.sample, etc. They do nothing until you (or a tool) strips the .sample suffix and marks the file executable.git commit --no-verify skips hooks entirely. Any pre-commit or commit-msg hook is opt-out for anyone with local access, not a hard security boundary — it's a fast local guardrail, not a substitute for the same checks running in CI where they can't be bypassed.commit-msg can rewrite or validate message format, post-checkout can auto-install dependencies when package.json changed on the branch you just switched to, pre-push can run a broader suite than pre-commit affords time for. The hook points are named after Git lifecycle events, not after "linting."This is the same move as this track's earlier lesson on ignore rules — turning something Git leaves as a local, per-machine default into a file that's tracked, reviewed, and identical for every clone. A hook lives in the repo the same way a .gitignore or a CI workflow file does: it's policy, expressed as a file, that travels with the code instead of living in one person's memory.