Why can't client-side Git hooks be trusted to enforce a mandatory engineering policy?
answer
- Where does the hook file actually live
- Does clone bring it along
- There is a documented flag for skipping
- No audit trail for the bypass
- Feedback layer versus guarantee layer
basics
~20 sClient-side hooks live in .git/hooks, which is not part of the repository content and never travels with a clone, and any developer can skip them with --no-verify or by deleting the file. They are fast feedback, not enforcement.
solid answer
~50 sTwo independent facts sink them as a guarantee. First, **they are not distributed**: the hook directory lives inside `.git`, which holds objects, refs and local configuration — not tracked content — so cloning a repository never installs anybody's hooks. A rule that only exists on the machine of whoever wrote it is not a rule. Second, **they are trivially bypassed by design**: `git commit --no-verify` skips `pre-commit` and `commit-msg`, `git push --no-verify` skips `pre-push`, and since the hook is just a file the developer owns, deleting it or clearing its executable bit works too. There is no audit trail for any of that. Commits created by `git rebase` do not run `pre-commit` at all. The right framing is layered: client hooks buy fast, local, *pre-mistake* feedback, and anything that must actually hold has to be re-checked where the change is received, on infrastructure the developer does not control.
code
console · 6 lines$ git commit -m "emergency fix"
pre-commit: 3 files fail formatting
$ git commit --no-verify -m "emergency fix"
[main 51ab903] emergency fix
$ git log -1 --format='%H %s'
51ab903... emergency fixgo deeper
Remember two facts: hooks live in .git and are not copied when someone clones, and --no-verify skips them. That alone explains why they are advice, not enforcement.
Explain both failure modes precisely — non-distribution and documented bypass — and add the coverage gaps, such as rebase-replayed commits never running pre-commit.
Demonstrate the layered design: fast advisory checks locally, a binding check where changes are received, and a deliberate mechanism to stop the two from drifting apart.
Own the framing for the organisation: state plainly which controls are real and which are conveniences, resist reporting a hook as an enforced policy, and decide who owns the authoritative rule definition.
## The claim being tested "We enforce our commit rules with a pre-commit hook" is one of the most common half-truths in interviews. It is worth separating what client hooks genuinely deliver from what they cannot. ## Reason one: hooks are not repository content Git's hook directory is `.git/hooks` — inside the administrative directory, alongside `objects`, `refs`, `config` and `index`. None of that is versioned content. When someone clones, Git transfers objects and refs and then creates a fresh administrative directory from its template, which is where the inert `.sample` files come from. Your carefully written `commit-msg` hook is not in the transfer, was never in a tree, and has no commit history. The consequence is stark: a new hire is not covered, a CI checkout is not covered, a machine reimaged last week is not covered. Even for the people who did install the hook, there is no mechanism keeping their copy current — three developers can be running three different vintages of the rule with nothing reporting the drift. ## Reason two: bypass is a supported feature Git does not treat hooks as a security boundary, and it says so by shipping the escape hatch: - `git commit --no-verify` skips `pre-commit` and `commit-msg`; - `git push --no-verify` skips `pre-push`. That is deliberate. Hooks are the developer's own tooling, and there are legitimate moments — an emergency fix, a hook that is broken, a commit of deliberately unformatted fixture data — where skipping is correct. But the same switch is available to anyone in a hurry, and nothing records that it was used: the resulting commit is an ordinary commit, indistinguishable from one that passed. And because the hook is a plain file the developer owns, `rm .git/hooks/pre-commit` or clearing the executable bit is an equally effective bypass that no one will ever notice. ## Reason three: not every commit goes through the hook Even with a hook installed and never bypassed, coverage has gaps. Commits replayed by `git rebase` do not run `pre-commit`, so content that would have been blocked when first written can land through a rebase. Merge commits go down a different path from ordinary commits. A tool or GUI that constructs commits through Git's lower-level machinery may not trigger hooks at all. "The hook was installed" therefore does not imply "every commit in this branch satisfied it". ## What client hooks are actually good for None of this makes them worthless — it makes them the wrong layer for a guarantee. Their real value is **latency**. A formatting problem caught in 200 ms at commit time costs nothing; the same problem caught minutes later after a full remote verification cycle costs a context switch, and caught in review it costs a second human. Client hooks compress that loop, and a developer who fixes issues before sharing looks more competent, which is its own incentive to keep them installed. ## The layered answer an interviewer wants 1. **Client hooks** — instant, local, advisory. Optimise for speed and clear messages; scope them to what is staged. Accept that they are bypassable and design for that rather than pretending otherwise. 2. **A binding check where changes are received or verified** — running on infrastructure the developer does not control, which is where a rule becomes a guarantee. 3. **Make the two agree.** The failure mode of a layered setup is drift: the local check and the binding one enforce different versions of the rule, so "it passed locally" stops meaning anything. Deriving both from the same configuration is the discipline that keeps the fast layer honest. ## The wrong conclusions Two mistakes bracket this topic. One is treating a hook as a control and reporting to an auditor that a policy is enforced when it is one flag away from being skipped. The other is deleting the local hooks because "they prove nothing" — that throws away the fastest feedback in the whole loop to make a point about rigour. Keep the seatbelt; do not confuse it with a lock.
- Given that, is there any point installing a pre-commit hook at all?Yes — for latency, not for enforcement. A check that fails in a fraction of a second while the change is still in your head costs nothing; the same failure discovered later costs a context switch, and discovered in review it costs someone else's time. Treat client hooks as the fast advisory layer and put the binding check where the change is received.
- Would tracking the hook scripts in the repository fix the enforcement problem?It fixes distribution, not enforcement. Versioned hook scripts mean everyone can get the same rule and see it change over time, but the developer still owns their machine and `--no-verify` still exists. Distribution and enforcement are separate problems, and only the second one requires a check running somewhere the developer does not control.
- How would you answer an auditor who asks whether commit-message policy is enforced?Honestly: a local hook gives contributors immediate feedback but is advisory and bypassable with a documented flag, leaving no trace. The enforceable statement has to rest on a check that runs when the change is received, because that one cannot be skipped from a workstation. Claiming a client hook as a control misrepresents what Git guarantees.
- Why can a commit that violates your pre-commit rule appear even when nobody used --no-verify?Coverage gaps. Commits replayed by `git rebase` do not run `pre-commit`, merge commits take a different path, and some tooling builds commits through lower-level machinery that never invokes hooks. So an installed hook does not imply every commit on the branch satisfied it.
A client-side hook is the seatbelt warning chime in your own car: excellent at catching an honest lapse, and utterly powerless over a driver who decides to ignore it — which is why speed limits are enforced somewhere other than the dashboard.
saying these in an interview costs you the question
- Claiming hooks are copied by git clone
- Presenting a pre-commit hook as a security control
- Forgetting that --no-verify exists and leaves no trace
- Assuming every commit path runs pre-commit
- Deleting local hooks entirely because they prove nothing