---
title: "Three Bugs Got Past Review and the Pre-Push Guard Never Fired"
canonical: https://dxdev.com/blog/guard-dead-everywhere/
datePublished: 2026-08-30
---
A deleted line in a colleague's branch would have wiped an account's Stripe keys the next time they saved their payment options. CI didn't flag it. A linter didn't flag it. We caught it because we read the diff by hand.

A colleague had rebuilt the fundraiser admin panel and pushed it up for review. We went through it line by line and found three separate defects on the branch, already pushed, already past whatever automated checks were supposed to run:

- The deleted line that would silently wipe an account's stored Stripe keys on their next save.
- An Active toggle that never reverted in the UI when its save call failed, so the screen kept showing "on" after the backend had already said no.
- Group names flowing straight into link-dialog HTML unescaped, which is XSS-in-waiting the moment someone names a group `<script>`.

Every one of those is the category of thing a pre-push guard exists to catch. We have one. It's a [hook that runs before code leaves](/blog/when-your-guard-only-catches-one-shape/) a developer's machine, and it's supposed to run on every clone of a large legacy codebase. So the question wasn't only how three bugs got through. It was why the thing whose entire job is stopping this never fired.

## Checking whether the guard actually ran

We checked that colleague's clone first, since that was the branch in front of us. The guard wasn't running there. Our first assumption was a local misconfiguration, maybe a missing hook install step from when they cloned the repo. So we checked ours. Also dead. We checked a third clone. Same result.

That ruled out "one developer's setup is stale" and left us with "the guard has not been protecting anyone, on any machine, for however long this has been true." It's a different bug from the one we walked in expecting to file. We filed a ticket for it: the pre-push guard is dead in every clone of the codebase. Not flaky, not intermittent. Dead everywhere it was supposed to be running.

In the same pass we also found that the review skill we'd been using to triage tickets was assuming the wrong developer on attribution. That was a separate bug, so we fixed it before moving on.

## What "dead" actually meant

The failure mode matters more than the discovery. A pre-push hook that blocks a push loudly is annoying but safe: you know something stopped you, and you go find out why. A pre-push hook that silently declines to run and lets the push through is worse than no hook, because it gives you the confidence of a safety net without the net. Everyone who touched that codebase, us included, had been pushing on the assumption that a set of checks stood between code we wrote and the shared branch. None of it did.

We didn't get a full trace of why the guard stopped running on every clone at once, and I won't overstate the root cause. What we know comes from what shipped after. The fix that landed didn't try to stop the guard from ever crashing. It made the crash loud. The follow-up commit title says it plainly: the pre-push guard [degrades open when the guard itself crashes](/blog/safety-guards-fail-open-at-scale/).

Before that, a guard that threw an exception on startup could fail in a way indistinguishable from success: no error, no warning, the push just goes through. After the fix, a guard crash still lets the push through, deliberately, because blocking every developer's work over a bug in the safety check is its own outage. But now it says so. You get a visible warning that the check didn't run, instead of silence that reads as a pass.

That's the tradeoff we made: fail open, but always say so. Failing closed on guard crashes was the other option, and we rejected it because a bug in the guard shouldn't hold every developer's pushes hostage until someone fixes the guard. But failing open silently is what happened here, and it let three real defects, one of them a data-loss bug against live Stripe keys, sit on a pushed branch with nobody, human or automated, having looked.

## The gap between a file and a check

The bugs got caught, and that's the good half of this. Manual review is still the backstop, and it worked. But "manual review caught it" is only what you fall back to when the automation you were counting on has stopped counting. The guard existed in the repo, was referenced in onboarding docs, and was assumed by every developer who'd been told it was there. It did none of the work its presence implied. The distance between "the file is in the hooks folder" and "the check runs on every push" stayed invisible until someone read a diff closely enough to ask why it hadn't been caught.

If you rely on a pre-push or pre-commit guard, find out when you last watched it fire and block something. If you can't say, you may have a folder with a script in it that nothing calls. And make sure a crash in the guard itself produces a visible warning, so a skipped check never reads as a pass.
