Gemini and a solver found Go crashes that Google’s own fuzzer missed
Google's Karl Weinmeister paired Gemini 4 Argon with the Z3 solver to find Go panics. Go's fuzzer missed two planted bugs until one line of test code was changed.

Google’s Karl Weinmeister, who helps lead its agent skills program, describes in a Google Developers Blog post a way to find Go crashes with Gemini 4 Argon and the Z3 solver. It caught all three bugs he planted and three latent ones in his own tool.
How the Gemini and Z3 pipeline works
The target is skill-lint, a Go command-line tool Weinmeister wrote to check SKILL.md files and apply LLM-generated line edits. Google’s agent skills program currently has 175 skills, according to the post. The tool already had unit and fuzz tests, but the post says slice or line-number mistakes can sit dormant until an unusual file or edit range triggers a panic.

The method starts with the compiler. Go turns every slice or index operation it cannot prove safe into a branch to runtime.panicBounds. Weinmeister compiled each function for RISC-V with GOOS=linux GOARCH=riscv64 go build -gcflags="-S -d=ssa/check_bce/debug=1" to list the checks that survive optimization. He gave the Go source and that disassembly to Gemini 4 Argon, which wrote a Z3 script modeling the path to each remaining trap. If Z3 returned a satisfiable result, the resulting bytes were replayed with go test against the real function.
Argon’s translation took about a minute per function, while Z3 finished in milliseconds, per the post. He chose RISC-V because Anthropic’s earlier formal-verification work targets it.

Why Go’s fuzzer missed a crash hitting one pair in five
The first planted bug, in a function called ParseTwoChunkFrame, adds two uint8 values, so the sum wraps at 256 before the length check but not when the slice end is computed. At 59 bytes, 19.4% of possible header and body length pairs crash it. Z3 returned a 59-byte input with a header length of 48 and a body length of 236.
Yet unseeded go test -fuzz missed it in one 3-second run and three 60-second runs on Go 1.27.3. The reason, according to the post: Go checks slice expressions against capacity, not length, and the fuzzer almost never supplies a slice without spare room. Weinmeister sampled ten seconds of fuzzing, 386,741 calls in all, and found that 89% of inputs were windows onto one common block of memory of about 100 MB. Just 0.07% had capacity equal to length. A second planted bug, ExtractQuotedToken, was missed for the same reason.

Only 0.07% of fuzz inputs had capacity equal to length.
The fix is one line in the fuzz target: data = data[:len(data):len(data)]. With it, the fuzzer crashed both functions in all runs, with median times of 0.28 s and 0.17 s. The third planted bug, a signed int8 length error, was found by the fuzzer either way.
Three real bugs in skill-lint
Of five extracted functions, two scanners came back clean and three had bugs. The sharpest was in ApplyReplacementChunks: edits are sorted bottom to top, but overlapping chunks are not rejected. After one edit shrinks the slice, a second can panic with slice bounds out of range [4:3], or silently overwrite earlier work. The other two were accounting errors: line numbers checked before deletions shrank the slice, and a prompt packer that reserved a flat 100 bytes for a roughly 70-byte wrapper plus file path.
Weinmeister notes that in all six buggy functions the first satisfiable answer triggered a real panic, with no false positives, partly because each extracted helper included its own entry guards. Stripping comments from the test files did not change the results, he says. Once helpers were pulled away from file I/O, a 3-second fuzz run also caught all three skill-lint bugs.
A cheap middle path between fuzzing and proofs
This is a single developer’s test on a handful of functions, reported by Google itself, and it has not been independently replicated. The solver approach is explicitly not a proof: Weinmeister writes that an LLM-written model is not a sound proof system, and an “unsat” answer only means “maybe correct”. Its value is that any hit is a reproducible Go panic.
The contrast with the heavier route is clear. Our earlier report on how Claude wrote 180 million lines of proofs to check 123 open-source libraries described full machine-checked verification, and the post says that is still too heavy for most day-to-day Go projects. Weinmeister’s practical advice is tiered: fuzz pure helpers with capacity clipped to length first, and bring in the solver for traps that survive that, such as parsers computing offsets from multi-field headers. Concurrency bugs stay with the race detector, since the models are single-threaded.
The post also fits a pattern around Argon’s security work. Google’s own social post on 21 October said researchers at Wiz found Gemini 4 Argon surfaced more than twice as many vulnerabilities as baseline models in real-world testing. Both are vendor-reported results. The post does not say whether Google will package the pipeline as a tool, and it covers only functions small enough to extract: a whole 28 MB skill-lint binary carries 32,321 symbols, too many to hand to a solver directly.
Sign Up for the Daily AI Pulse
One email a day. All the stories that matter.
By signing up, you agree to our Terms of Use and acknowledge the data practices in our Privacy Policy. You may unsubscribe at any time.
Alex CarterAI News & Big Tech CorrespondentAlex Carter writes for Aiholics, keeping readers updated on the fast-paced world of AI and Big Tech.



