Contents

Series "Learning to Code with AI" — Article 5/7
RSS
RSS sends new articles to the reader of your choice, without an algorithm or newsletter.
TL;DR
AI can produce code very quickly. It can't spare you from knowing what actually changed, what was verified, and what you'll be able to fix tomorrow. Git, diffs, and tests aren't bureaucracy added around the agent: they're the tools that allow you to keep control.
"It's done" isn't a technical report
Claude Code or Codex can complete a task and tell you everything is ready, that tests pass, and that the code follows the project's conventions.
This response is convenient. It's not proof.
The proof is in the repository: in the files that changed, in the dependencies that were added, in the commands actually executed, and in the observed behavior. This is an important difference, especially when starting out. If you never look at the concrete result, you don't develop the ability to verify. You develop trust in the agent's narrative.
The problem isn't that the agent can make mistakes. We all make mistakes when developing. The problem is no longer being able to see when it made a mistake.
Our mini-dashboard is a good example to understand this. Imagine we want to add a CPU chart. A reasonable request should touch the metric component, demo data or API retrieval, and a corresponding test. If such a limited task also modifies the deployment configuration, adds three dependencies, and reorganizes the entire directory structure, it's not necessarily wrong. But it's definitely a discussion to have before accepting the change.
The diff is where the promise meets reality
Before letting the agent modify anything, start by knowing where you stand. A git status shows you the already modified files. A git diff shows you the uncommitted work. This reflex avoids a very simple mistake: attributing to the agent a change you started the day before, or overwriting your own work by going back too quickly.
Next, ask for a plan before the code. Not an architecture novel. A very concrete plan: which files will be modified, why, which tests will be added, and which commands will verify the result. This plan creates an expectation. The final diff then becomes something you can compare to an intention, instead of being a block of technical text whose logic you try to guess after the fact.
For the CPU chart, your exchange can be as simple as this:
Don't modify any files yet.
I want to add only the display of the CPU metric in the mini-dashboard.
Indicate the relevant files, the behaviors to cover, any dependencies, and the validation commands.
If a change goes beyond this task, explain why and wait for my approval.
You're not asking the AI to be less autonomous. You're imposing a verifiable scope. This is exactly what an experienced developer would expect from a task review.
When the change is ready, don't start with the most impressive part of the code. Start with the filenames. They often tell a clearer story than the line details. Then look at package.json and the lock file: a display chart doesn't necessarily require an additional library. Then move up to the system boundaries, where the code retrieves an external response, reads an environment variable, or transforms data. These are the places where errors quickly become costly.
Finally, read the nominal behavior and the cases that fall outside the ideal scenario. What happens if the API doesn't respond? If the CPU value is zero? If it's missing, negative, or greater than one hundred? A screen that doesn't crash isn't automatically a screen that tells the truth.
Small changes make understanding possible
A large diff creates an illusion of speed. The agent creates an API, four charts, error handling, tests, responsive design, and an abstraction layer all at once. The result might even work on the first try.
But it becomes very difficult to review. And when behavior is wrong, you don't know where to look or which decision introduced the problem.
The right pace is to build a first chart, handle its error case, verify its test, and only then generalize. It's slightly less spectacular. It's much more educational. You can explain each step, identify when an abstraction becomes useful, and return to the last healthy state if something breaks.
Git isn't just there to save files. A well-structured commit is a unit of reasoning. It should tell a modification you can summarize without recounting your entire day. Adding a CPU chart, protecting the display from an invalid value, or isolating a formatting function are three different decisions. Separating them allows you to verify and question them separately.
Tests aren't there to reassure the agent
There's a very misleading way to work with tests: asking the agent to write a few at the end, seeing that they pass, and then considering the feature validated.
A passing test only proves that the program behaved as that test expected. It doesn't prove that the test protects a useful situation.
Before writing the test for our CPU chart, formulate the behavior in plain English. A value of 42 should be displayed with its unit. Zero remains a valid value. Missing data shouldn't disguise itself as zero. An impossible value should be handled explicitly. A retrieval error shouldn't make the entire dashboard disappear.
From there, the AI can become excellent. It can suggest missing cases, challenge a weak assertion, or explain why a test depends too much on the component's internal structure. But the testing strategy shouldn't come only from what it has already coded. Otherwise, it risks verifying its own implementation instead of verifying the need.
A good reflex is to intentionally break, for a few seconds, the behavior you think you're protecting. If your test stays green when the CPU value is no longer displayed, it's not protecting what you think. You then restore the correct code, of course. But you've just obtained more solid proof than the simple "tests passed" message.
The Next.js testing documentation is useful here: it reminds us that tools don't cover all the same behavior levels. A unit test, an integration test, and a manual verification don't provide the same information. Looking for the perfect tool is less useful than knowing precisely what you're trying to verify.
What's verified must be distinguished from what's assumed
At the end of a task, ask the agent to tell you exactly which commands were executed, with what result, and what wasn't verified. Typing, linting, tests, and the build each provide different information. If one couldn't run, it's not a disaster. Hiding it, however, deprives you of a decision.
This requirement changes many things in a team. "Tests should pass" doesn't carry the same weight as "npm test was executed and here's the result." The first phrase is an intuition. The second is an observation.
Permissions play the same role. Installing a dependency, running a migration, accessing the network, or pushing code aren't neutral actions. You don't need to forbid everything to the agent. You need to know when its action becomes risky enough to require human validation. Settings evolve, so the documentation for Codex and Claude Code remain the best references for their current capabilities.
Review is a skill that's learned
At first, reading a diff is tiring. You feel like you're wasting time facing an agent capable of producing ten files in a few minutes. Then one day, you spot an unnecessary dependency, data transformed too early, a test that tests nothing, or an out-of-scope change before it becomes a production bug.
At that moment, you understand that review isn't a formality. It's a developer skill. And it's also one of the skills that makes AI truly useful: you can go faster because you know where to look, what to ask, and when to say no.
In the next article, we'll apply this same logic to debugging. Not to ask the agent to make an error disappear, but to learn how to trace back to its real cause.
