Friday, 4:47 p.m. Claude Code just finished a “small” fix: the dashboard filter was ignoring archived accounts. The chat summary is cheerful. Tests are “probably fine.” You glance at the green checkmarks in the terminal, feel the pull of the weekend, and almost type git push without opening the diff. Almost. Then you open it. Forty-three files. A deleted test suite. A new constant named API_KEY with a real-looking string. The original bug fix is somewhere in the middle of a reformat that rewrote half the utils folder. You close the laptop for a second, not because the model is evil, but because shipping blind is how small fixes become incident tickets.
This is Part 9 of the Claude Code tutorial. You already installed, explored, used slash commands, skills, memory, project instruction files, plugins, and multi-step agent loops. Autonomy without a review habit is not productivity. It is deferred debugging with extra confidence. This part is the brake: how to read diffs, undo safely, spot smells, and keep a human hand on the commit.
What you’ll learn
- A five-step review loop you can run after every agent change set
- How to read a diff like a skeptical teammate, not like a grateful passenger
- Undo tools that matter:
git restore,git checkout, and carefulgit reset - Why force-push is almost never the first answer
- A diff smell checklist (unrelated files, deleted tests, secrets, huge rewrites)
- A short practice drill you can do on a throwaway branch this week
Product UIs and default permission modes move. Treat commands below as patterns. Re-check Claude Code docs and your team’s git policy before you write a policy memo for the whole company.
Why “it compiled” is not a review
Claude Code is good at producing plausible change sets. That is the product. Plausible is not the same as correct, complete, or scoped. A change can:
- Pass a unit test that never covered the real edge case
- Fix the bug and also “clean up” three unrelated modules you did not ask for
- Delete a flaky test instead of fixing the flake
- Hardcode a secret “just for local” that later lands on a shared branch
- Rename something globally in a way that breaks a plugin you forgot existed
If you only look at the chat summary, you review the story the agent told about the work. If you look at the diff, you review the work. Those are different jobs. Analysts already know the pattern from AI-written SQL: fluent narrative, wrong grain. Same class of risk with code. For the SQL version of this habit on AMS, see How to check AI-written SQL before you ship it.
The review loop (do not skip steps)
After Claude finishes a multi-file change, run this loop in order. Make it boring. Boring is the point.

1. Read the diff
Start with the file list, not the first hunk. Ask: does this list match the ticket? Open each file that is outside the obvious blast radius and ask why it is there. Then read the hunks that touch business logic, auth, money, PII, migrations, and config. Skim pure formatting only after you know nothing dangerous hides inside a “style” commit.
Useful git views (run them yourself; do not only trust the agent’s paraphrase):
# What changed?
git status
# File list + short stats
git diff --stat
# Full unstaged patch
git diff
# Staged only
git diff --cached
# Against main (adjust branch name)
git diff main...HEADIn an IDE, the same idea is a side-by-side diff for every touched file. Terminal or GUI is fine. The non-negotiable is that a human eyes the actual patch before the commit leaves your machine (or at least before it hits the shared default branch).
2. Run the tests that matter
“The agent said tests passed” is a rumor until you run them in your shell (or see CI on the PR). Prefer the project’s documented commands from CLAUDE.md, README, or package scripts. If the project has a fast suite and a slow suite, run the fast suite always and the slow suite when the change touches risk paths (auth, payments, data migrations, shared libraries).
# Examples only. Use your repo’s real scripts.
npm test
npm run typecheck
npm run lint
# Or
pytest -q
go test ./...
cargo testIf tests fail, fix or roll back. Do not “commit green later.” If tests pass but you never understood them, you still have not reviewed the logic. Tests reduce risk. They do not replace reading the auth change.
3. Check for secrets
Search the diff for keys, tokens, connection strings, private URLs, and customer data samples. Agents sometimes invent placeholders that look real, or paste from local env files into source. Both are bad. Placeholders that look real confuse the next human. Real secrets in git are an incident.
# Crude but useful smell search on the patch
git diff | rg -i 'api[_-]?key|secret|password|token|BEGIN (RSA |OPENSSH )?PRIVATE|AKIA[0-9A-Z]{16}'
# Also check newly added files
git status --shortIf something secret landed in the working tree, remove it before commit. If it already committed to a shared branch, follow your security process (rotate the secret, scrub history only with people who know what they are doing). Do not casually rewrite public history alone at 5 p.m.
4. Check scope
Scope is the question: “Did we only do the job we asked for?” Agents love helpful extras: rename for consistency, extract a helper, update docs, reformat imports, upgrade a dependency “while we’re here.” Helpful extras can be good on a dedicated cleanup PR. On a bugfix PR they hide the real change and expand the blast radius of review.
A practical rule: if the ticket was “fix filter on archived accounts,” the PR should mostly be the filter, tests for the filter, and maybe a one-line comment. If the PR is also a TypeScript upgrade, that is two tickets wearing one jacket.
5. Human commit
You (or a teammate with ownership) write the commit message and decide what lands. Let Claude draft a message if you want, then edit it until it is true. “Assorted improvements” is not a message. “Fix archived-account filter in dashboard query; add regression test” is a message.
git add path/to/relevant/files
git commit -m "Fix archived-account filter in dashboard query"
# Push a feature branch, not necessarily main
git push -u origin fix/archived-filterDefault habit: feature branch + pull request. Direct push to main is a team policy decision, not a model permission prompt. Part 10 covers team rails. For this part, assume you still own the commit button.
Rule of thumb: Chat summary is a pitch. Diff is evidence. Tests are a second opinion. Commit is a signature.
Diff smells: stop and reassess
Use this checklist when something feels “off,” or as a routine gate for larger agent runs. Any one item is enough to pause. Two items means you almost certainly need to split, revert, or re-prompt with a tighter goal.

| Smell | What it often means | What to do |
|---|---|---|
| Unrelated files changed | Scope creep or wrong root cause | Restore those files; re-ask for a narrower edit |
| Deleted tests without a clear reason | Agent “fixed” red by removing proof | Restore tests; fix the code or mark skip with a ticket |
| Hardcoded secrets or keys | Env confusion or bad example code | Remove, rotate if needed, use env/secret store |
| Huge rewrite for a tiny bug | Overfitting to a vague prompt | Reset; restate the bug with file/line constraints |
| “Just trust me” commit message | Nobody can review intent later | Rewrite message; if you cannot, you do not understand the patch |
| New dependencies you did not ask for | Convenience over control | Demand justification; check license and supply chain |
| Permission or auth code touched “by accident” | High blast radius | Slow down; second reviewer; extra tests |
When you find a smell, say it out loud to the agent in the next turn: “Revert changes outside src/filters/ and the related test. Do not reformat. Do not touch package.json.” Tight constraints after a bad run work better than scolding the model for being creative.
Undo without making it worse
Undo is a skill. The wrong undo turns a local mess into a team mess. Learn the safe defaults first.
Discard uncommitted edits (usually safest)
If Claude edited files and you have not committed, you can throw away changes per file or for the whole tree.
# Throw away unstaged edits to one file (modern git)
git restore path/to/file.ts
# Throw away all unstaged edits in the working tree
git restore .
# Unstage without discarding contents
git restore --staged path/to/file.ts
# Older equivalent people still use
git checkout -- path/to/file.tsgit restore is the clearer modern command for “make the working tree match HEAD (or another source).” git checkout -- file still works in many setups but does two different jobs in older docs (switch branches vs restore files). Prefer restore when your git version supports it. Official reference: git-restore and git-checkout.
Undo a local commit carefully
If you committed only on your machine and have not pushed (or only pushed to a personal branch you fully control), you can move the branch pointer.
# Soft: undo commit, keep all changes staged
git reset --soft HEAD~1
# Mixed (default): undo commit, keep changes unstaged
git reset HEAD~1
# Hard: undo commit AND discard those changes (destructive)
git reset --hard HEAD~1--hard is a shredder for uncommitted and reset commits. Use it when you are sure you want the agent’s work gone. Prefer --soft or default mixed when you still want to salvage pieces. Official reference: git-reset.
Force-push is dangerous
Force-push rewrites history on the remote. On a shared branch it can erase a teammate’s commits, break open PRs, and confuse CI. Even on a personal feature branch it can surprise anyone who already pulled. Treat git push --force and git push --force-with-lease as tools for known, deliberate recovery, not as the default cleanup after an agent mistake.
If Claude suggests force-pushing to “clean up” a bad commit on main, that is a red flag. Stop. Prefer a reverse commit, a new fix commit, or a controlled reset only if your team’s process explicitly allows it and you understand who else has the branch.
# Safer pattern for a bad commit already on a shared branch:
# add a reverse commit instead of rewriting history
git revert HEAD
git pushIf the agent keeps offering history rewrites, restate policy in the chat and in CLAUDE.md: “Never force-push shared branches. Prefer revert. Ask before any history rewrite.”
Worked example: the “small filter fix”
Imagine you asked:
In src/dashboard/query.ts, include archived accounts only when
includeArchived is true. Add a unit test. Do not reformat other files.
Do not change dependencies.Claude returns a cheerful “done.” You run the review loop.
$ git diff --stat
src/dashboard/query.ts | 12 ++++--
src/dashboard/query.test.ts | 28 +++++++++++++
src/utils/format.ts | 140 ++++++++++++++++++----------------
src/utils/dates.ts | 88 +++++++++++---------
package.json | 2 +-
tests/legacy/dashboard.spec.js | 210 --------------------------------
.env.example | 1 +
7 files changed, 200 insertions(+), 281 deletions(-)What the stats tell you before you even open hunks:
query.tsandquery.test.tslook on-mission.format.tsanddates.tslook like unrelated rewrites.package.jsonwas not requested.- A large test deletion is a classic smell.
.env.examplemight be fine or might hide a bad pattern; open it.
Recovery path:
# Keep the good files, drop the rest of the agent’s surprise
git restore src/utils/format.ts src/utils/dates.ts package.json tests/legacy/dashboard.spec.js
# Inspect remaining diff carefully
git diff
# Run tests
npm test -- src/dashboard/query.test.ts
# Commit only the intentional paths
git add src/dashboard/query.ts src/dashboard/query.test.ts
git commit -m "Respect includeArchived in dashboard query"Then tell Claude what you did and why, so the next turn does not re-apply the junk:
I restored format.ts, dates.ts, package.json, and the legacy dashboard spec.
Keep only query.ts + query.test.ts for this task. Do not reformat utils.
Do not delete tests. Confirm with git diff --stat before more edits.That conversation pattern matters as much as the git commands. Agents will happily re-expand scope if you only delete files quietly and keep chatting as if the wider plan still stands.
Permissions help, review still wins
Claude Code’s permission model (approve before many tool uses, configurable allow/deny rules, modes that trade speed for caution) is real protection. It is not a substitute for reading the patch. Approving “write to these three files” still requires you to understand what was written. Skipping prompts with high-trust modes raises speed and risk together. Anthropic documents permissions and modes in the Claude Code docs; start from Configure permissions and treat --dangerously-skip-permissions style flags as specialist tools, not defaults for a shared production repo.
If Part 8 (agent loops and autonomy) left you tempted to walk away while the agent runs, this part is the answer: walk away only when rails exist (tests, no secrets, narrow scope, branch isolation) and you still plan a human review before merge. Autonomy without a merge gate is cosplay.
A lightweight personal policy you can paste into CLAUDE.md
Short and true beats long and aspirational:
## Safety and review
- Prefer smallest change that fixes the ticket.
- Do not reformat unrelated files.
- Do not delete or skip tests without explicit approval.
- Never commit secrets, .env files, or customer data samples.
- Do not force-push shared branches; prefer git revert.
- After multi-file edits, stop and show git diff --stat; wait for human review.
- Human owns the final commit message and push.Project instruction files from Part 6 of this series only help if they match real behavior. If your team ignores the policy, fix the team habit (Part 10), not just the markdown.
Common mistakes
- Reviewing only the chat recap. Recaps omit silent deletions and drive-by refactors.
- Trusting a single green unit test for an integration risk. Match test depth to blast radius.
- Using
reset --hardon a branch you already shared. You may destroy other people’s work. - Force-pushing to “make the history pretty.” Pretty is optional. Recoverable is not.
- Leaving deleted tests because CI was annoying. You traded a red bar for a future incident.
- Committing .env “just this once.” Once is how secrets get into forks and laptop backups.
- Letting the agent commit and push unattended on day two of using Code. Earn autonomy after you can undo and review without panic.
- Huge prompt: “clean up the whole codebase while fixing the bug.” You ordered the mess.
Practice: 25 minutes on a throwaway branch
- Clone or open a safe repo (personal project or a training fork).
- Create a branch:
git checkout -b practice/review-loop. - Ask Claude Code for a small feature with tight constraints (one folder, add tests, no reformat).
- Run the five-step loop and fill the smell checklist out loud or in a note.
- Intentionally ask for a second messy change (“also clean up utils”). Practice
git restoreon the extras. - Make one local commit, then practice
git reset --soft HEAD~1and re-commit cleanly. - Do not force-push anything shared. End with a normal push of the practice branch or delete the branch locally.
How this fits the series
Earlier parts taught you to start, explore, command, skill up, manage memory, write instruction files, add tools carefully, and run multi-step loops. This part is the professional filter between “agent produced files” and “team can live with this.” Part 10 closes the Claude Code tutorial with team habits: shared CLAUDE.md, PR requirements, CI, secrets policy, and teaching juniors review before high autonomy.
If you are still mapping which Claude product you need day to day, the Claude product map and Learn Claude from scratch series stay useful. More paths: Learn hub.
Quick recap
- Always: read diff → run tests → check secrets → check scope → human commit.
- Diff smells: unrelated files, deleted tests, secrets, huge rewrites, vague messages.
- Undo with
git restore/ carefulreset; treat force-push as exceptional and dangerous on shared history. - Permission modes reduce accidents; they do not read the patch for you.
- Write review rules into
CLAUDE.md, then follow them yourself so the agent has a model of good behavior.
Next: Part 10, Team habits and safety rails, closes this series and points you toward Claude Cowork for non-repo agent work and the earlier map/learn tracks when you need product orientation instead of a terminal.
Sources
Docs and related reading used for review, undo, and permission habits:
- Claude Code documentation: Overview (product behavior and entry points; re-check before policy write-ups)
- Claude Code documentation: Configure permissions (permission rules, modes, and that rules are enforced by the product, not only by prompt text)
- Anthropic Engineering: How we built Claude Code auto mode (default approve-before-act posture and tradeoffs of skipping prompts)
- Git: git-diff (inspecting patches before you trust a summary)
- Git: git-restore (discard or unstage working tree changes)
- Git: git-checkout (older restore patterns; branch switching vs file restore)
- Git: git-reset (soft/mixed/hard; know what each destroys)
- Git: git-revert (undo on shared history without force-push)
- OWASP Top 10 for LLM Applications (over-reliance and sensitive information risk classes)
- Analytics Made Simple: How to check AI-written SQL before you ship it (same “fluent ≠ correct” verify habit)
- Analytics Made Simple: Learn (related paths on this site)
