skip to content

You own a Go repo's merge queue: which of gofmt, go vet and a generate-diff check may block a merge?

level: principalimportance: nice to knowfreq 28%

answer

  1. someone pays for every merge
  2. deterministic, actionable, fast, useful
  3. cheapest and most mechanical goes first
  4. advisory before required
  5. measure what each gate caught

basics

~20 s

Block on checks that are deterministic, fast, and fixable by the author in minutes: formatting, then vet. Keep the regenerate-and-diff check advisory until its version pinning is proven. Every blocking check must run locally with one command.

solid answer

~50 s

I score each candidate gate on four properties: is it deterministic, is the failure actionable by the pull request author alone, how long does it add to every merge, and how often has it actually caught something. A formatting check scores well on all four, since the fix is mechanical and takes seconds, so it blocks. `go vet ./...` blocks too, because the analyzers shipped with the toolchain are chosen for a low false-positive rate — but only with the Go release pinned, or an upgrade quietly adds analyzers and reddens a repository nobody touched. The regenerate-and-diff check is the most fragile of the three, depending on pinned generators, a pinned Go release and deterministic output, so I run it advisory first and promote it once its red runs have been honest. Anything blocking must also be reproducible locally with the same one command.

go deeper

for a junior

Understand that a check blocking a merge costs every teammate time on every change, and that the first thing you should be able to do is run that same check locally before pushing.

for a middle

Be able to argue why formatting and vet are safe to require while a regenerate-and-diff check needs pinned versions first, and to name what makes each of them deterministic or not.

for a senior

Show the operational side: pinning the toolchain so an upgrade does not redden untouched code, printing the fix into the log, and rolling a new gate out advisory-first with the backlog cleared in its own commits.

for a principal

Own the policy and its reversal. State the properties each gate is trusted for, budget the queue's wall-clock time, keep a visible break-glass, and be willing to delete a gate whose catch rate never justified its cost.

## The decision, not the commands All three checks are easy to write. The question a merge-queue owner actually answers is *which of them is allowed to stop other people's work*, and that is a cost decision, not a technical one. A blocking gate spends everyone's time on every change; it earns that by preventing something worse. Framing the answer as "turn them all on" is the weak version, because it ignores who pays. ## Four properties I score a candidate gate on **Determinism.** Does the same commit always produce the same verdict? A gate that is sometimes red for reasons unrelated to the change trains people to re-run it, and once they re-run reflexively the gate has stopped working even when it is right. **Actionability by the author.** Can the person whose pull request went red fix it themselves, in minutes, from the information in the log? A formatting failure that prints the diff is perfectly actionable. A failure that says only "files differ" and requires local tooling nobody has installed is not. **Cost per merge.** The gate runs on every change forever. Seconds are free; minutes multiplied by a busy queue are a real tax and a real incident risk when something urgent must ship. **Catch rate.** What has it actually prevented? This is the property teams never measure and the one that most often justifies removing a gate. A check that has never failed except spuriously is not protection, it is ceremony. ## Scoring the three **Formatting.** Deterministic for a fixed toolchain, mechanically fixable, effectively instant. It blocks. The one caveat is the toolchain version: formatter output has changed across Go releases, so a floating Go version turns a deterministic gate into an intermittent one. Pin it, and treat any repo-wide reformat as its own commit rather than something an unlucky author absorbs. Also make sure the failing log shows the diff, not just a list of paths. **Vet.** The analyzers shipped with the toolchain are deliberately conservative — they aim at things that are almost certainly bugs — so the false-positive rate is low enough to block on, and the failures it catches are real defects rather than style. The operational hazard is the same one: the analyzer set grows with releases, so an unpinned Go version can redden a repository that nobody changed. Pin the release, and schedule the upgrade as a deliberate piece of work with a budget for whatever the new analyzers find. **Regenerate-and-diff.** The most valuable of the three when it works and the most fragile. It depends on pinned generators, a pinned Go release, deterministic generator output, and correctly tracked generated paths. I introduce it as advisory, watch what it reports for a few weeks, fix the determinism and pinning problems it exposes, and promote it to blocking only when its red runs have been genuinely correct. If the repository does not commit generated code at all, I do not add the gate — I add the generation to the build instead. ## The rules I hold every blocking gate to 1. **One command, both places.** CI invokes exactly the script an engineer can run locally. A gate whose incantation exists only in the pipeline definition costs a full round trip per iteration and is the single biggest source of resentment toward checks. 2. **The log contains the fix.** Print the diff, the file, the line. "Check failed" is a defect in the gate. 3. **Versions are pinned and upgraded on purpose.** Both the Go release and any tools the checks run. Drift between a laptop and the pipeline is the failure mode that makes people distrust every check, including the ones that were right. 4. **Break-glass exists and is visible.** There must be a documented way to merge without a gate during an incident, and using it must be noticeable afterwards. A gate with no override is one incident away from being turned off permanently. 5. **Gates are reviewed, not accumulated.** Periodically ask what each one caught. Something that has cost minutes per merge for a year and caught nothing is a candidate for demotion to advisory or for deletion — and being willing to remove a gate is what makes it credible to add one. ## Rolling out a new gate Never flip a new check to blocking on a large repository at once: the first run reports the accumulated drift of the repository's whole history, and the cost lands on whoever happens to be pushing. Run it advisory, fix the backlog in dedicated commits, then require it. If the backlog is too large to fix at once, require the check only for the packages a change touches, and shrink the exemption over time. ## Where the judgment can be overruled This is a decision an engineering lead can reasonably reverse — for example, when delivery pressure makes the queue's wall-clock time the binding constraint, or when a gate's failures have been dominated by version skew. The defensible position is not "all checks always block"; it is having stated which property each gate is being trusted for, and being able to show what it has caught.

  • How do you add a new blocking check to a large repository without stopping everyone?
    Run it non-blocking first and let it report the accumulated backlog, which on an old repository is the history's drift rather than anyone's change. Fix that backlog in dedicated, reviewable commits separate from feature work. Only then make it required. If the backlog is too large to clear at once, scope the requirement to the packages a change touches and shrink the exemption over time.
  • A required vet gate suddenly fails across the repository and nobody changed the code. What do you do?
    First establish that it is a toolchain change: the analyzer set that ships with the go command grows between releases, so a runner that picked up a newer Go version can report findings on untouched code. Pin the release, get the queue moving again, then treat the new findings as their own piece of work with an owner and a budget. Upgrading the toolchain is a scheduled change, not something an unlucky author absorbs.
  • When would you remove a gate rather than add one?
    When its catch rate does not justify its cost: it has run on every merge for months, added minutes each time, and either never failed or failed mostly for reasons unrelated to the change. Demote it to advisory or a nightly run and see whether anything gets worse. Being willing to remove a gate is what makes it credible to require the next one.

saying these in an interview costs you the question

  • Makes every available check blocking on day one
  • Cannot say what a gate has actually caught
  • Runs checks only in the pipeline with no local equivalent
  • Leaves the Go version floating on the runners
  • Treats a repo-wide reformat as one unlucky author's problem
  • Provides no documented way to merge during an incident