What does `go test`'s `-fuzzminimizetime` flag control when a fuzz target hits a failing input?
answer
- saving the crasher is not the first step
- a smaller repro costs wall-clock time
- a budget per attempt, not per run
- a duration, or Nx iterations
- the default is sixty seconds
basics
~20 sIt bounds how long the fuzzing engine spends shrinking a failing input before saving it. After a failure Go repeatedly retries smaller variants that still fail; -fuzzminimizetime caps each such minimization attempt, and the default is 60s.
solid answer
~50 sWhen a `-fuzz` run finds a failing input it does not save it immediately: it first tries to **minimize** it, re-running the target on smaller or simpler variants and keeping ones that still fail. What ends up in `testdata/fuzz/<FuzzName>/` is the minimized version, which is why a saved crasher is usually a readable forty-byte frame instead of the four kilobytes of noise that originally tripped the parser. `-fuzzminimizetime` bounds each minimization attempt: it takes a duration such as `-fuzzminimizetime=10s`, or an iteration count with the `Nx` form such as `100x`, and defaults to 60s. Lowering it gets you a crasher sooner but a larger, uglier one; raising it spends wall-clock time buying a smaller repro. And if the target is not deterministic, minimization can wander onto a different failure, so always confirm the saved entry still reproduces the bug you care about.
code
text · 1 line$ go test -fuzz=FuzzParseFrame -fuzzminimizetime=10s ./proxygo deeper
Know that Go shrinks a failing input before saving it, and that the file you end up with under testdata/fuzz is the shrunken version, not the original random bytes.
Be able to state what the flag bounds, that it accepts a duration or an Nx iteration count, and that its default is 60s per minimization attempt.
Demonstrate the tradeoff in practice: shorter budgets for interactive hunting, longer for scheduled runs whose output a human reviews, and recognise flaky replay as a non-deterministic target.
The angle to own is quality of the artefact your process produces - a bloated crasher becomes a permanent, unreviewable test, so minimization budget is part of what you standardise for the repository.
### Minimization: the step between "found a crash" and "saved a file" Coverage-guided fuzzing finds failures by mutating inputs, so the input that finally breaks a binary frame parser is usually mostly irrelevant. It may be kilobytes long, with one malformed length prefix buried inside a mass of bytes that had nothing to do with the bug. Handing that to a reviewer is close to useless. So before writing anything to disk, the engine tries to **minimize**: it constructs smaller and simpler candidates derived from the failing input, runs the target on each, and keeps any candidate that still fails. The result is a much smaller input that triggers the same code path. That minimized input is what gets written to `testdata/fuzz/<FuzzName>/<hash>`, and it is what you review and commit. ### What the flag actually bounds `-fuzzminimizetime` sets the budget for each minimization attempt. Two forms are accepted: - a duration, for example `-fuzzminimizetime=10s` or `-fuzzminimizetime=2m`; - an iteration count using the `Nx` syntax, for example `-fuzzminimizetime=100x`, meaning run the target that many times during the attempt. The default is `60s`. The budget is per minimization attempt, not for the fuzzing session as a whole - the session's own budget is a separate flag entirely, and confusing the two is the most common mistake with this option. When the budget expires, the engine stops shrinking and saves whatever it had reached. Nothing is lost by a small budget except size: you still get a genuine failing input, just a less tidy one. ### Choosing a value - **Interactive debugging on a laptop.** Lower it. You mostly want the crash file to exist so you can start iterating with `go test -run='FuzzParseFrame/<hash>'`; a slightly bulky input is fine, and waiting a minute per failure is annoying when you are hunting several. - **A scheduled or overnight run whose output a human reads in the morning.** Leave the default, or raise it. Nobody is waiting, and a smaller entry means a clearer bug report, a cheaper committed regression test and a diff that reviews easily. - **A target that is expensive per call.** Minimization is just repeated target invocations, so if a single parse takes milliseconds, a 60s budget buys far fewer shrink attempts than it does on a microsecond-scale parser. Think in iterations, not seconds; that is exactly what the `Nx` form is for. ### The determinism caveat Minimization rests on an assumption: running the target on the same input gives the same verdict. If the target is non-deterministic - it keeps state between calls, depends on map iteration order, involves timing, or touches shared globals - that assumption breaks. The engine can then shrink into an input that fails for a different reason, or produce one that does not reliably fail at all. The symptom is unmistakable and worth recognising: the saved corpus entry reproduces the panic only sometimes. When that happens the corpus entry is not the problem; the target is. Make the fuzz target a pure function of its arguments - construct fresh state inside it, avoid globals, avoid wall-clock dependence - and re-run. Only then is the minimized entry worth committing, because only then will it be a stable regression test for everyone else who runs `go test`. ### Why this matters for what you commit Everything about the committed corpus argues for small entries. A minimized input is faster to run on every build, easier to review, easier to reason about when it fails again in two years, and less likely to be an unnecessarily rich piece of attack material if the repository is public. Minimization is the step that produces that quality, and `-fuzzminimizetime` is the dial that decides how much you pay for it.
- Why does a minimized crasher matter more than the raw failing input?Because a human has to read it. A minimized frame usually points straight at the faulty branch - a length prefix larger than the bytes that follow, say - while the original input is mostly irrelevant noise around it. It also keeps the committed entry small, so the permanent regression test is cheap to run and the diff is reviewable.
- Minimization finished, but the saved entry only reproduces the panic sometimes. What happened?Almost certainly a non-deterministic target: state carried between calls, dependence on map iteration order, timing, or shared globals. Minimization assumes re-running on the same input gives the same verdict; when it does not, the engine can shrink into a different failure or into none. Fix the determinism first, then trust the corpus entry.
- How do you pick a value for a target where one call is expensive?Think in iterations rather than seconds and use the `Nx` form, for example `-fuzzminimizetime=200x`. A 60s budget on a parser that takes milliseconds per call buys far fewer shrink attempts than on a microsecond-scale one, so a fixed duration gives wildly different minimization quality across targets.
saying these in an interview costs you the question
- Thinks the flag bounds the whole fuzzing run
- Believes the raw failing input is what gets saved
- Assumes minimization guarantees the same failure
- Confuses the minimization budget with the fuzzing budget
- Blames the corpus file when replay is flaky