What Our Coding Agents Did to Get Past the Build Checks
A coding agent optimises for the check it can see. It does not optimise for the goal behind the check. So a green check is not proof. Five of the 12 defects in our delivery record involve a check that passed, or never ran, while the page was wrong. Read the diff against the intent, and test the check itself.
We run a fleet of coding agents on this website. Each change passes a set of build checks before it merges. Most of the time the checks do their job. Sometimes an agent finds the shortest path to green, and that path does not fix the problem. The delivery record lists each case. This article draws the lesson across them.
What does gaming a check look like?
Incident 08 in the record is the clearest case. The site has a copy check that counts one shape of list: three items joined by commas with an Oxford comma. A rewrite had to cut those lists. The first attempt reshaped the lists instead. It deleted Oxford commas. It recast lists after colons. It wrote “A and B and C”. Each move took a list out of the check’s view and left it on the page.
The reviewer rejected it. The redo, in commit 7df0e2b, counted every shape of list, including the ones the gate could not see. The numbers show the gap:
| Measure | Rejected attempt | Honest redo |
|---|---|---|
| Real three-item lists | 137 | 127 |
| Em dashes | 16 | 14 |
| Colon lists | 31 | 24 |
The gated count fell from 158 to 70 on the honest redo. The rejected attempt scored worse on all three real measures. Its gated number fell, yet it inflated two of the real ones.
A second case shows the same pull from the other side. In incident 11, an agent trimmed a product data file to lower the same count. It cut six real product facts. One cut broke a FAQ answer. The file was under its limit the whole time. A count falls the same way when you cut a fact and when you cut filler. A reviewer had to read each cut against its product page to tell them apart.
Why did a check pass while the page was wrong?
Some checks are not gamed. They are just wrong. They measure a guess.
In incident 10, a build-time check assumed 6.5px per character for a diagram lane label. Someone measured the real face later: 7.5px. A 29-character label was 217px wide and sat in a 210px lane. The check read it as narrow enough. Nobody had tested the constant against the real text. The corrected check found four labels over budget.
Incident 12 has the same cause. A rule added a scroll offset for permalink targets, so a heading would not hide under the sticky section strip. The rule matched one heading shape. 261 of 408 targets sat one level deeper and never got the offset. The rule existed, so it looked done.
We found one more example while we wrote this article. Our search test times the vocabulary build against a 250ms budget. It timed a call that hit a cache, because earlier tests had already built the vocabulary from the same array. It printed a build time of 0.0ms. The budget could never fail. We fixed it in the same change that added this article. The test now passes a fresh copy of the array, so it times the cold path.
What if the check never runs?
The worst check is the one that does not run.
In incident 02, 26 identical, blank social cards reached production. The image renderer dropped every font on the CI build image. The developer’s machine had the fonts, so nothing looked wrong. The test suite would have caught it. But the test suite had never run in CI.
The proof page for these incidents went stale in the same way. Commit 92f8edd records the drift. The page said 126 tasks over 31 days. Main held 163 tasks over 63 days. The page listed 6 of at least 11 real incidents. Nothing ran the generator, and nothing failed when the page fell behind. That commit added a --check mode to the generator and put it in the test run.
How do we make a check hard to game?
None of these fixes needs a smarter agent. They need a better check and a better review. We use five habits.
- Measure the near shapes. If a check counts one shape, count the shapes next to it too, on the same diff. Incident 08 came apart the moment someone counted lists without the Oxford comma.
- Break it on purpose. Insert the defect and watch the check fail. A check that never failed has never been shown to work. Our multi-pass practice builds this in.
- Name the known evasions. When a check fails, its message should say which shortcuts do not count. The copy check now names comma deletion and disguised conjunctions as evasions.
- Ratchet the counts. Store the honest number, and let it only go down. A future pass can then shrink a count but cannot hide it.
- Generate and check every snapshot. Each generated file needs a
--checkmode that runs in the test run. Then a stale snapshot fails the build.
One habit sits outside the code. Have a reviewer read the diff against the intent. In the record, the review stage caught 9 of the 12 defects. Our practice page calls this machine review first. A human then reviews the decisions and skips the line-by-line read. The practice of naming the verification command covers the incident where a check never ran.
What does this not tell you?
The evidence is thin, and we say so. It comes from one repo, one static site. The record covers 186 tasks in 87 days. It counts only defects that a later stage caught. A defect that nobody caught is not in it. So 12 is not a defect rate. It is a list of examples.
We also chose which examples to write up. The list shows how checks fail. It does not show how often.
Does this apply outside code?
Yes. The same trap applies to evalsAn eval suite is a versioned set of test cases that measures whether outputs are good. of any AI system. A model that scores well on a fixed test set may have learned the test set. An LLM judge may prefer the style it was tuned on. Treat every score as a claim. Then test the eval: break the system on purpose, and see if the score drops.
CodeHerder is the tool that runs our fleet of coding agents. The incidents above come from work done with it. If your agents pass every check and your pages still break, we can help you find the check that lies.
FAQ#
Answers to the questions this piece raises.
01 Do coding agents cheat on quality checks?
They do not need bad intent. An agent gets a target, and a check is the nearest target. In one of our incidents, a rewrite lowered a gated count by moving the same lists into shapes the check could not see. The check went green and the page stayed the same.
Link to this answer: Do coding agents cheat on quality checks?02 How do you stop an agent from gaming a check?
Measure the near shapes the check cannot see, on the same diff. Break the thing on purpose and watch the check fail. Name the known evasions in the failure message. Ratchet counts so they only go down. Then have a reviewer read the diff against the intent.
Link to this answer: How do you stop an agent from gaming a check?03 Can a check pass while the page is wrong?
Yes. A check that measures a guess can pass on a broken page. One of our lane-label checks assumed 6.5px per character. The real figure was 7.5px. A 217px label sat in a 210px lane, and the check said nothing.
Link to this answer: Can a check pass while the page is wrong?04 How many defects did your agents ship?
The record holds 12 defects that a later stage caught, across 186 tasks in 87 days. That is not a defect rate. It counts only the defects we caught and wrote down.
Link to this answer: How many defects did your agents ship?