ai newsSa., 31. Okt.
EilmeldungGoogle Deemind kartiert weltweit Urwald, Plantagen und Ölpalmen auf zehn Meter genau

Shipping a ticket with Claude Code: TDD, proof, review loop

From October 1 to 7, the same code change was reviewed fifteen times in a row by an AI. Each review is called a round. In round 7, the AI found nothing to say: the change held only nine changed lines. In round 8, it held 400, and the AI started hunting for defects again. Nobody had set a maximum nu

dev.toSamstag 10:00 · 31.10.10 Min. Lesezeit
Bild: dev.to

Posted on Oct 31 • Originally published at web-developpeur.com

From October 1 to 7, the same code change was reviewed fifteen times in a row by an AI. Each review is called a round.

In round 7, the AI found nothing to say: the change held only nine changed lines. In round 8, it held 400, and the AI started hunting for defects again. Nobody had set a maximum number of rounds.

That is the trap: "zero defects" does not mean "done", because the code keeps changing. If nobody writes down when to stop, the review goes on as long as the code changes.

This review AI is a skill of Claude Code. Claude is Anthropic's AI, and Claude Code is the programming assistant you drive from the keyboard. A skill is a folder of instructions that the assistant loads for a type of task. Mine is called senior-review.

Since then, every step of my work has a stop rule you can check. I tell it through a real case, shipped on October 27.

That real case is a ticket: a written request, such as a bug to fix, delivered as one reviewed change. A second skill, ticket, chains five steps for me: plan, code, proof, review, wrap-up.

Each step has an owner. It is often an agent, an instance of Claude launched for one precise task. I describe the method, not the skill file.

Planning, coding and reviewing are three different jobs. I give them to three different agents. Each runs on a model of the Claude family: Fable, Sonnet or Opus.

A comparison: the architect, the mason and the building inspector. The mason does not sign off on his own wall. I explained in another article why the reviewer must never be the author.

The plan lists the steps, the files to touch and the tests to write first. I launch everything with one sentence: "plan fable execution sonnet /senior-review opus". The ticket skill reads it word by word.

an executing agent, on Sonnet, writes the code

If I say nothing more, the skill applies that same trio.

This choice of models is a setting, not a measurement. The diagram shows the five steps and their owner.

Chain of a ticket in five steps. 1, plan by Fable. 2, execution by Sonnet: the test fails, then passes. 3, proof by me: I undo the fix and look. 4, review by Opus, stop when a round changes nothing, five rounds at most. 5, wrap-up approved by me. An important defect sends the work back from the review to the execution. 1. Plan model: Fable ✓ steps, files, tests to write 2. Execution model: Sonnet ✓ the test fails, then passes 3. Proof me, live ✓ I undo the fix and look 4. Review model: Opus ✓ stop if nothing changes, 5 rounds max 5. Wrap-up approved by me ✓ 3 rules at most, to approve ✗ important defect: back to step 2

Each step has its owner and a result you can check. In red, the way back when the review finds an important defect.

My contrast checker tells whether a text stays readable on its background, for example grey on white. Contrast measures the gap in lightness between the two.

It had a defect. A link pasted into a mockup review, where people comment on the drawing of a screen, reopened the tool with its usual colors. The real ticket is called "Share a contrast check through a link".

The two colors are written at the end of the URL, the address of the page:?fg=767676&bg=ffffff. For this grey on white, the tool must show the contrast ratio, the number it computes: 4.54.

The ticket adds two requirements. Changing a color updates the URL without reloading the page, with history.replaceState. An invalid value such as?fg=zzz brings back the usual colors, with no error.

A test is a small program that checks that the code does what you expect. TDD means writing that test before the code. It fails first (red), then passes (green) once the code arrives.

My test is e2e, end to end: a real browser opens the real page, like a user. It uses Playwright, a tool that drives a browser from code.

Real result of the first run, before any code (excerpt; the test titles are in French):

✘ 5 › 1.?fg=767676&bg=ffffff: couleurs du lien appliquées, AA normal réussi (8.9s) Expected: "767676" Received: "1A1A2E" 5 failed 2 passed (10.9s) Enter fullscreen mode Exit fullscreen mode The message names the defect: the field shows 1A1A2E, the usual color, instead of 767676. The test fails for the right reason. It computes the expected ratio itself with the official formula (WCAG).

Two tests already passed. One checks my own ratio calculation. The other is the?fg=zzz case, which worked even without the feature. It guards against a future break, but proves nothing about reading the URL.

A Sonnet agent then writes the code. It starts from a brief, written instructions that inherit nothing from my session: the plan, the allowed files, the bans.

A green test proves nothing until you have seen it fail without the fix. Reverse mutation means undoing the fix. The test must then go back to red.

If it stays green, it was not checking what you thought. Mutation testing automates this move across a whole project: see my article on mutation testing.

Proof by undoing, in three steps. 1, without the fix, the test is red. 2, with the fix, the test is green. 3, fix reverted, the test must turn red again. If red, the test proves something. If green, the test proved nothing. 1. Without the fix the test fails for the right reason ✗ red 2. With the fix the code is written the test passes ✓ green 3. Fix reverted we remove the fix it must turn red ✗ red ✓ Red: the test proves it the fix works ✗ Green: it proved nothing even without the fix

The third step is the one people skip: undo the fix and watch the test turn red.

Before the review, on the first draft of the code, I undid the fix in four ways. I removed the reading of the URL, then its writing. I replaced replaceState with pushState, its cousin that adds a page to the history. I removed the check on values.

Each attempt made at least one test fail. More followed during the review, and one made nothing turn red: that is the story of round 2. Real results of two attempts (kept verbatim, in French):

=== M1: lecture de l'URL retirée 4 failed 3 passed (10.7s) === M2: replaceState retiré 1 failed 6 passed (2.2s) Enter fullscreen mode Exit fullscreen mode Then I look at the page like a user, with screenshots at three sizes: mobile (390×844), tablet (820×1180) and desktop (1280×900).

My proof report separates three things, one line each:

On this ticket, two areas have no test. No test clicks the "Apply" buttons. The language switch links lose the colors of the link.

Before trusting the agent, I rerun the project's automatic checks myself, then I read each screenshot. Below, the same link at the three sizes, taken on the live site.

The same link at the skill's three sizes. On mobile the page is small: you look at the layout, not the text.

All three screenshots show 4.54, with no horizontal scroll. The method for catching display bugs with Playwright is in my article on responsive testing.

On this ticket, two reviewers worked in parallel in rounds 1 and 2, one on the code and one on the tests. Only one remained in round 3. All ran on Opus, five reviewers in total.

They are "blind": they do not know who wrote the code or what the author thinks of it.

A reviewer flags defects. Each flagged defect is called a finding, blocking if it prevents adding the code to the project, important if it must be fixed before moving on.

The rounds form a loop: review, fix, review again. It stops when a round meets three conditions:

A change that only touches tests counts as a change: another round is due. At the fifth round without a conclusion, the loop stops and hands control back to me.

My reviewers were only meant to read, and I had written it in their instructions. In fact they had every right, Edit and Write included: they could modify files. An instruction is only a request. Only the agent's definition really forbids an action.

In the tool, you pick a color with a picker. When you move it, it signals an event at every small movement. My code rewrote the URL at every event.

Now WebKit, Safari's engine, accepts at most 100 rewrites in 10 seconds. Beyond that it returns an error, SecurityError. The URL then stays frozen on an old color, while the displayed ratio is correct.

Playwright can launch that engine, and a reviewer reproduced the bug with it. Chromium, Chrome's engine, ends on the right URL. The fix: write the URL 250 ms after the last event, not at each one.

The reviewer also found three gaps in my tests:

I replaced 250 ms with 0 in the code. All nine tests stayed green, so none really watched the delay. The reason: my test sent the 150 events at once, too fast for the delay to change anything.

The test now spaces its events by 16 ms, the pace of a drag. Real results, excerpt (kept verbatim, in French):

# M-a: debounce retiré (setTimeout 250 -> 0) 9 passed (10.3s) # Tour 2, M-c: setTimeout 0 avec rafale espacée de 16 ms 1 failed 8 passed # Tour 2, code restauré 9 passed (5.2s) Enter fullscreen mode Exit fullscreen mode Two more remarks. Leaving the page before 250 ms loses the last color of the URL: rejected, no human copies a URL in 250 ms. The ticket announced "4.54:1", the tool shows "4.54": a badly worded criterion, not a bug.

A last reviewer raised nothing. The delay test passed 90 times out of 90, even under load, with 4 CPU loops at 100%. The code did not change: the loop stops.

Total: 9 findings. 5 fixed, 1 rejected, 3 deferred, meaning put off until later. Those 3 are only two topics. The first is the rewrite of the bare URL at load: seen by two rounds, so counted twice. The second is the wording gap in the ticket, which is not a code defect.

My review skill keeps a journal: one line per review, with the findings raised, confirmed and dropped. As of October 27, it showed 981 confirmed findings. But "confirmed" only meant "passed my filter". The journal did not say whether I then fixed the defect or refused to fix it. I tell that discovery, figures and cost per finding included, in my article on AI code review.

The journal now records the outcome of each confirmed finding: fixed, rejected or deferred. This ticket is the first review to carry it: 5 fixed, 1 rejected and 3 filed as "deferred". That is one real deferral seen twice, plus the wording gap in the ticket.

So I announce no acceptance rate: over a single review, it would mean nothing. Nor can I say that Fable plans better, or that Opus reviews better than Sonnet. It is my setting, not a result.

Last step, the wrap-up. I reread my corrections from the session ("did you test it?", "are you sure?"): each one reveals a missing rule. The skill proposes at most three to me, with their destination. Nothing is written without my approval. This ticket produced one: a test that proves a delay spaces its events.

Every rule in this chain was born from a failure observed from the outside. A loop that ran fifteen times. "Read-only" reviewers who could write. A journal that graded itself with its own filter.

I have no before and after measurement: I promise you neither time savings nor quality gains. I offer two moves for tomorrow. Write the stop condition of your loop before you launch it. Undo your fix before you trust your test.

Quelle: dev.to (dev.to). Original öffnen