<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Daniel</title>
    <description>The latest articles on DEV Community by Daniel (@legacy2latest).</description>
    <link>https://dev.to/legacy2latest</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4155666%2F27871c42-d041-488f-9f27-528b94dca313.png</url>
      <title>DEV Community: Daniel</title>
      <link>https://dev.to/legacy2latest</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/legacy2latest"/>
    <language>en</language>
    <item>
      <title>Claude Code made 6 mistakes upgrading a legacy Spring Boot service. None reached a commit.</title>
      <dc:creator>Daniel</dc:creator>
      <pubDate>Tue, 06 Oct 2026 16:12:12 +0000</pubDate>
      <link>https://dev.to/legacy2latest/claude-code-made-6-mistakes-upgrading-a-legacy-spring-boot-service-none-reached-a-commit-l8d</link>
      <guid>https://dev.to/legacy2latest/claude-code-made-6-mistakes-upgrading-a-legacy-spring-boot-service-none-reached-a-commit-l8d</guid>
      <description>&lt;p&gt;We gave Claude Code a job nobody wants to do by hand: a full framework upgrade of a legacy service. Java 11 and Spring Boot 2.7 at the start, with Springfox for the API docs, the old &lt;code&gt;WebSecurityConfigurerAdapter&lt;/code&gt; that Spring Security 6 removed, and a TODO from 2019. Java 25 and Spring Boot 4.1 at the end.&lt;/p&gt;

&lt;p&gt;It took about two hours of Claude time and roughly $24 at API prices. It ended green, 47 tests passing.&lt;/p&gt;

&lt;p&gt;The result isn't the interesting part. On the way, Claude made six mistakes, and not one of them made it into a commit. Here they are, straight from the session transcripts, with whatever caught each one.&lt;/p&gt;

&lt;p&gt;The service is a demo, built on purpose to look like the legacy code a lot of us keep alive (a parcel tracking REST API called ShipTrack). The sessions are real.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. "56 tests pass." The build ran 47.
&lt;/h2&gt;

&lt;p&gt;After the move to Java 21, Claude modernized the code and wrote a tidy summary. Everything in it was accurate except one number: it said 56 tests passed. The build had run 47.&lt;/p&gt;

&lt;p&gt;Small, but it's the cleanest example of the whole problem. A model's summary describes the work. It doesn't prove it. The evidence is in the build output.&lt;/p&gt;

&lt;p&gt;That's why the definition of done in our &lt;code&gt;CLAUDE.md&lt;/code&gt; doesn't say "tests pass". It names a command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Definition of done&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`./mvnw verify`&lt;/span&gt; passes.
&lt;span class="p"&gt;-&lt;/span&gt; No new deprecation warnings in the compiler output.
&lt;span class="p"&gt;-&lt;/span&gt; UPGRADE_NOTES.md says what a reviewer should double-check.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Caught by:&lt;/strong&gt; reading the build's own output, not the summary.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. A good plan with two wrong claims in it
&lt;/h2&gt;

&lt;p&gt;For the jump to Spring Boot 3.5, Claude didn't touch code at first. It went into plan mode, explored with subagents, and wrote a plan: every file to change, the order of operations, even the limits our hooks put on the work. A good plan.&lt;/p&gt;

&lt;p&gt;It also contained two false statements. It said Spring Boot 3 no longer had a path-matching property (it still does). And it said &lt;code&gt;server.max-http-header-size&lt;/code&gt; kept its name in 3.5. It didn't: since 3.0 it's &lt;code&gt;server.max-http-request-header-size&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Checking took one command. Spring Boot jars ship their own configuration metadata, including every deprecated key and its replacement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unzip &lt;span class="nt"&gt;-p&lt;/span&gt; path/to/spring-boot-autoconfigure-3.5.x.jar META-INF/spring-configuration-metadata.json &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="s1"&gt;'.properties[] | select(.name | test("header-size"))'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A long plan with two wrong claims is normal. That's exactly why you review a plan like a pull request, before any code changes. A wrong assumption costs one sentence to fix in a plan. After the edits, it costs an afternoon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caught by:&lt;/strong&gt; plan review plus the configuration metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The decision our own guardrail forced it to make
&lt;/h2&gt;

&lt;p&gt;This one is my favorite, because it was partly our fault.&lt;/p&gt;

&lt;p&gt;After the jump to 3.5, two tests failed. Since Spring Framework 6.1, unknown URLs go through the static resource handler, and the 404 body now says "No static resource". A real change to the public API.&lt;/p&gt;

&lt;p&gt;Claude did the right thing: it laid out two options and asked us to choose. Then it tried to stop and wait for an answer.&lt;/p&gt;

&lt;p&gt;Our Stop hook said no. Its job is to keep Claude working until the build is green, and the build was red. So Claude picked option B itself, got the build green, and flagged the choice for review.&lt;/p&gt;

&lt;p&gt;The reasoning was sensible. But a product decision that belonged to a human was made by the model, and our guardrail pushed it there. The fix went into the hook: when the only failing tests are characterization tests, a red build means behavior changed, and that's a question for a person. Now the hook lets Claude stop and ask.&lt;/p&gt;

&lt;p&gt;For reference, the wiring is just two entries in &lt;code&gt;.claude/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PreToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Edit|Write"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".claude/hooks/protect-characterization-tests.sh"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Stop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".claude/hooks/require-green-build.sh"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first one blocks edits to the tests that freeze today's behavior (exit code 2 blocks the tool call). The second one refuses to end the turn on a red build. If you copy the idea, give the Stop hook a way out for "this needs a human", or it will make decisions for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caught by:&lt;/strong&gt; reading the transcript. The reviewer subagent flagged it too.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Green build, ship verdict, and the API had changed anyway
&lt;/h2&gt;

&lt;p&gt;Now the build was green, all 47 tests passed, and the reviewer subagent said "ship it".&lt;/p&gt;

&lt;p&gt;Before committing we did one boring thing: ran the old version and the new one side by side, sent the same unknown URL to both, and compared the real responses.&lt;/p&gt;

&lt;p&gt;Same status, same message. But the new body had an extra &lt;code&gt;details&lt;/code&gt; field, and the timestamp was formatted differently. Two changes to the public API, with every test green.&lt;/p&gt;

&lt;p&gt;Why? The test for unknown URLs checked four fields. It never checked that there was nothing else. A test only protects what it asserts.&lt;/p&gt;

&lt;p&gt;The fix started with the contract, not the code. A human made that test stricter (exact number of fields, exact timestamp format) and ran it against the old version first, to prove it described what clients really got. Then Claude got the evidence and one instruction: go back to Spring Boot's default error body and leave the tests alone. It removed its own handler and overrode just the message, inside Spring Boot's error attributes. Five fields, old timestamp format. Then we committed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caught by:&lt;/strong&gt; a human comparing real responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. A confident diagnosis. Also wrong.
&lt;/h2&gt;

&lt;p&gt;On to Spring Boot 4.1. Two characterization tests red again. In one, the error body for malformed JSON had lost its &lt;code&gt;message&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;Claude investigated and gave a clear diagnosis: Spring Boot 4 no longer puts the message in the response, so we'd need extra code to bring it back.&lt;/p&gt;

&lt;p&gt;Plausible. Wrong.&lt;/p&gt;

&lt;p&gt;Spring Boot silently ignores properties it doesn't recognize. One more look at the configuration metadata showed the key had been renamed in 4.0: &lt;code&gt;server.error.include-message&lt;/code&gt; became &lt;code&gt;spring.web.error.include-message&lt;/code&gt;. The old key was ignored, so the message disappeared. No code needed.&lt;/p&gt;

&lt;p&gt;The plan had also skipped &lt;code&gt;spring-boot-properties-migrator&lt;/code&gt;, which our upgrade instructions explicitly ask for. It exists to report exactly this kind of rename.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caught by:&lt;/strong&gt; the configuration metadata, again.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The build was green. The compile had failed.
&lt;/h2&gt;

&lt;p&gt;Last step, Java 25. Should be a one-line change. Claude switched the version, ran the build: green. Then it checked for warnings with something like &lt;code&gt;mvn compile | grep -i warning&lt;/code&gt;. Nothing. Looks done.&lt;/p&gt;

&lt;p&gt;Except that first build wasn't clean, so classes compiled earlier were still sitting in &lt;code&gt;target/&lt;/code&gt;. And the compile had actually failed. The error lines just didn't contain the word "warning", and the pipe swallowed the exit code.&lt;/p&gt;

&lt;p&gt;Before finishing, Claude ran one more full clean build, and the truth came out: &lt;code&gt;cannot find symbol&lt;/code&gt;. The Lombok getters were missing. Since JDK 23, &lt;code&gt;javac&lt;/code&gt; no longer runs annotation processors found on the class path unless you ask it to, so Lombok has to be declared in the compiler plugin.&lt;/p&gt;

&lt;p&gt;Claude recognized it and said its earlier check was flawed. Output you filtered is not a result. True for humans, true for models. Our Stop hook now runs a clean build whenever the &lt;code&gt;pom.xml&lt;/code&gt; changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caught by:&lt;/strong&gt; a clean build.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I take from it
&lt;/h2&gt;

&lt;p&gt;Look at the list of catches: the build output, a plan review, the transcript and the reviewer, a human comparing responses, the configuration metadata, a clean build. No single guardrail caught everything. Each one caught something the others missed.&lt;/p&gt;

&lt;p&gt;And Claude did almost all of the work, and did it well. It found a real production bug before the upgrade even started, refused to work around a guardrail, and checked APIs in the actual jars. The human part was small. But it was the part that mattered: building the guardrails and making the decisions.&lt;/p&gt;

&lt;p&gt;Every one of those guardrails is something you can add to your own repo this week.&lt;/p&gt;

&lt;p&gt;The full walkthrough, with the transcripts on screen:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/Ptif0tYYOYk" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Personal project, views my own. The sessions, numbers and transcripts are real; this write-up was drafted with AI from the video and its transcripts.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>java</category>
      <category>springboot</category>
      <category>codereview</category>
    </item>
    <item>
      <title>How I review an AI agent's diff (the diff comes last)</title>
      <dc:creator>Daniel</dc:creator>
      <pubDate>Thu, 01 Oct 2026 19:08:41 +0000</pubDate>
      <link>https://dev.to/legacy2latest/how-i-review-an-ai-agents-diff-the-diff-comes-last-19fd</link>
      <guid>https://dev.to/legacy2latest/how-i-review-an-ai-agents-diff-the-diff-comes-last-19fd</guid>
      <description>&lt;p&gt;When an AI agent hands you a diff, reading it line by line is the last step, not the first.&lt;/p&gt;

&lt;p&gt;By the time you see the diff, the agent has already told you a story: what it changed, why it's safe, and that everything passes. A senior review checks that story against evidence. Here's the order I use, walked through on one real step: a legacy demo service moving from Spring Boot 2.7 to 3.5, done by Claude Code and replayed from its transcripts.&lt;/p&gt;

&lt;p&gt;Here's how that step ended, in the agent's own words:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The independent review confirms no hidden regressions. Green build. All 47 tests passing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those are claims. The question for each one is the same: can I check it against output the model didn't write?&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 1: the claims
&lt;/h2&gt;

&lt;p&gt;Scroll back to the final build. It ran in quiet mode, so a success prints almost nothing. Claude tried to read the exit code, and that command needed an approval nobody was there to give. So it concluded success from the absence of an error.&lt;/p&gt;

&lt;p&gt;It was right this time. The build really was green. But silence is weak evidence, and the same reasoning in another run could hide a failure.&lt;/p&gt;

&lt;p&gt;The fix is cheap: ask for the line the build itself prints, with the test count, pasted into the report. Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tests run: 47, Failures: 0, Errors: 0, Skipped: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the agent can't show you that line, you don't have a green build. You have a sentence about one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 2: the decisions
&lt;/h2&gt;

&lt;p&gt;Not the changes, the choices.&lt;/p&gt;

&lt;p&gt;There was one, flagged honestly at the end of the summary. A hook fired before a human could answer, so Claude defaulted to "option B": a choice about what the public API returns for unknown URLs.&lt;/p&gt;

&lt;p&gt;Maybe it's the right choice. That's not the point. A decision about your public API was made by the model, so it needs an explicit yes or no from a person before the step is done. Make a list of every choice like that in the session, and answer each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 3: a second opinion, and what it based its verdict on
&lt;/h2&gt;

&lt;p&gt;We use a reviewer subagent: one markdown file in &lt;code&gt;.claude/agents/&lt;/code&gt;, its own context, and no edit or write tools. It sees the diff, not the reasoning behind it. Its checklist is ordered by damage: public API first, then security, the frozen tests, configuration, dependencies. Every finding gets a severity, and the report ends with one verdict: ship, or fix first.&lt;/p&gt;

&lt;p&gt;The report earned its keep. It flagged the model's decision as an unreviewed change to the error body. It spotted deprecated annotations in two test classes that a hook had kept Claude from touching. And a security rule that was probably dead.&lt;/p&gt;

&lt;p&gt;Then the verdict: ship.&lt;/p&gt;

&lt;p&gt;Now read &lt;em&gt;how it knows&lt;/em&gt;. The build: green, because no output means success. The public API: the error tests pass unmodified, so no drift. The reviewer trusted the same tests the author trusted. A second opinion built on the same evidence isn't independent. It's a filter. Useful, but read its reasons, not just its verdict.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 4: compare real behavior, old against new
&lt;/h2&gt;

&lt;p&gt;This is the check that found the problem, and it's almost embarrassingly simple. Start the old version and the new one. Send both the same request. Compare.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# old version on :8080, new one on :8081 — adapt the paths and users to your API&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;path &lt;span class="k"&gt;in&lt;/span&gt; /api/parcels/does-not-exist /api/parcels/42 /api/stats&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; clerk:clerk &lt;span class="s2"&gt;"http://localhost:8080&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-S&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; old.json
  curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; clerk:clerk &lt;span class="s2"&gt;"http://localhost:8081&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-S&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; new.json
  diff &lt;span class="nt"&gt;-u&lt;/span&gt; old.json new.json &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"^^^ &lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="s2"&gt; changed"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An unknown URL on 2.7, then on the new build: a &lt;code&gt;details&lt;/code&gt; field that was never there, and a timestamp in a different format. Forty-seven green tests, a ship verdict, and the public API had changed.&lt;/p&gt;

&lt;p&gt;Why did the tests miss it? The test for that URL looked at four fields and ignored the rest of the body. A new field couldn't turn it red. Green meant those four fields were fine, not that the body was.&lt;/p&gt;

&lt;p&gt;Build your own list of requests your clients depend on. Put at least one request that should fail in it, and one that tests who's allowed. Every difference gets a human decision: bring the old behavior back, or keep the new one deliberately, with a note that says why.&lt;/p&gt;

&lt;p&gt;When a check like this finds a gap, fix the contract before the code. A person tightened the test (exactly five fields, the old timestamp format) and ran it against the &lt;em&gt;old&lt;/em&gt; version first. It passed there, so it described what clients really got. Then Claude got both real responses as evidence, a note of what the human had already changed, and one instruction with a clear finish line: restore the default error body exactly as on 2.7, and leave the tests alone.&lt;/p&gt;

&lt;p&gt;It took its own handler out and changed just the message through Spring Boot's error attributes. Same request again: five fields, old timestamp style, matches 2.7.&lt;/p&gt;

&lt;p&gt;One more habit from that exchange. Claude's last line separated its edits from the human's. In a diff with two authors, require that every time. You need to know whose work you're approving.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 5: what the agent admits it didn't check
&lt;/h2&gt;

&lt;p&gt;Claude's plan included calling the running app by hand. Launching it in the background needed an approval, and in a headless run there was no one to give it. So it dropped that check and wrote that the automated tests already covered it.&lt;/p&gt;

&lt;p&gt;Mostly. But not one test in the suite ever requested the OpenAPI description or the Swagger page. So those went on the human's list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no login, trailing slash added: still 401;&lt;/li&gt;
&lt;li&gt;logged in as a clerk: the parcel is still found;&lt;/li&gt;
&lt;li&gt;both documentation endpoints respond.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything the agent couldn't verify is your checklist. Look for the words "should", "already covered" and "assumed" in its report.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 6: the diff itself
&lt;/h2&gt;

&lt;p&gt;Only now. Read it for scope, and for the &lt;em&gt;next&lt;/em&gt; change, not just this one.&lt;/p&gt;

&lt;p&gt;In a later step (Java 25), Claude declared Lombok for the compiler plugin with a hard-coded version, and left a note: a human should bump it by hand if Lombok is ever upgraded. The review changed one line, to use the version property the Spring Boot parent already manages. Now the next framework upgrade moves the annotation processor and the library together.&lt;/p&gt;

&lt;p&gt;A note that asks for manual work later is a small bug with no date on it yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  The order, in one place
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Check the claims against output the model didn't write.&lt;/li&gt;
&lt;li&gt;List its decisions and answer each one.&lt;/li&gt;
&lt;li&gt;Get a second opinion, and read what it based its verdict on.&lt;/li&gt;
&lt;li&gt;Compare real behavior, old against new.&lt;/li&gt;
&lt;li&gt;Turn everything the agent couldn't verify into your own checklist.&lt;/li&gt;
&lt;li&gt;Then read the diff, for this change and the one after it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this is specific to Claude Code. Copilot, Cursor and Codex all document reviewer agents of their own, and the checks don't care which agent wrote the diff.&lt;/p&gt;

&lt;p&gt;The video version, with the transcripts and both responses on screen:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/eC0S_5HJTCk" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Personal project, views my own. The sessions and transcripts are real; this write-up was drafted with AI from the video and its transcripts.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>codereview</category>
      <category>testing</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
