Skip to content

Agentic Code Review with Copilot CLI

Lesson 3 of 9Intermediate13 min readGitHub Copilot & AI Engineering · Copilot CLIVerified: GitHub Copilot CLI command reference, September 2026

Self-review is the cheapest review layer and the one most often skipped, because assembling the diff and framing the question is friction at exactly the moment you want to be finished.

Copilot CLI removes that friction. /review and /diff operate on your working tree directly — no copying, no attaching, no leaving the terminal.

This lesson is what that buys and where it stops.

Terminal window
/review
/review src/payments/client.py

What it doesReviews code changes, or the contents of a specific file if one is named.

Why we run itIt reads your working tree directly, which removes the assembly step that makes self-review get skipped.

Expected resultReview findings for the changes or file in question.

Terminal window
{/* Review changes in the current directory */}
/diff

The distinction: /diff is oriented at what has changed; /review can also take a path and review a file’s contents whether or not it changed.

Both operate locally, on uncommitted work, before anything is pushed. That is the defining property of this layer.

Before the commit. Debug output, commented-out code, a hardcoded path, a TODO from twenty minutes ago, a test left with .only on it. The category that costs a review round trip and is embarrassing to have pointed out.

Before the push. The whole branch, read as a reviewer will read it. This is where you find the unrelated file and the change larger than you remembered.

On unfamiliar code you are about to change. /review path/to/file.py on something you did not write, asking what it does and what would break if you changed it.

Not for: proving anything. It has run nothing. Tests and CI are separate and unchanged.

“Review this” produces a list of observations of uneven value. The questions that produce findings:

“What did I leave in?” Highest yield, and instantly checkable.

“What does this change that the diff does not make obvious?” A changed default, a removed guard, a narrowed condition. Second-order effects are what human reviewers skim past.

“For each new code path, is there a test?” Absence is what a systematic reader catches and a human does not.

“What would a reviewer ask?” Pre-empts a round trip, and the answers belong in your pull request description.

“Is anything here security-relevant?” Broad, worth asking on anything touching input, auth, or command construction. Treat the output as prompts to check.

The agent can also run commands to answer these — checking whether a function is called elsewhere, whether a test file exists, whether the linter passes. That is the difference from pasting a diff into a chat window, and it is worth exploiting:

Review my staged changes. For anything you flag, check whether it is actually reachable by looking at the callers.

A review that can also edit is convenient and needs a boundary.

Reviewing is read-only. Nothing changes while it reads.

Applying a fix is an edit, and requires approval like any other. Read it.

The failure mode specific to this combination: accepting a suggested fix without evaluating whether the finding was real. The suggestion arrives with the finding, both sound plausible, and applying it is one approval away. If the finding was a false positive — the model lacking a file, usually — the “fix” is an unnecessary change to working code.

The habit that avoids it: decide the finding is real before looking at the fix. They are two judgements and the interface presents them as one.

The most useful thing to ask for, and the one nobody asks for: a confidence statement per finding.

For each finding, say how confident you are and what would change your assessment.

That turns an undifferentiated list into a triage order. High-confidence findings — a leftover debug line, a missing test file — are checkable in seconds. Low-confidence ones usually depend on code the model has not read, which tells you exactly what to supply.

It also surfaces the honest uncertainty that is otherwise absent. A finding presented flatly reads as a defect; the same finding presented as “possible, depending on whether process_order validates upstream — I have not read it” reads as what it is, which is a question.

The related instruction worth including in any review agent:

If a finding depends on code you have not read, say so explicitly rather than assuming.

Between them, those two lines address most of what makes AI review findings tiring to work through. The volume is not usually the problem; the problem is that every item arrives with the same apparent weight, so triage requires reading all of them carefully.

The same pattern as everywhere: findings that are wrong because the model saw the diff and not the system.

Missing error handling that is handled by a decorator or middleware it did not see.

Unused variables used in a file it was not given.

Style objections contradicting your linter, which is authoritative.

Security concerns about test fixtures, where the credential is deliberately fake — worth a second of thought each time, because occasionally the fixture is real.

Suggestions to add checks made unreachable by your type system.

The fix is nearly always to supply the missing context rather than to argue. In the CLI this is easy: tell it to read the file.

That is handled by the @handle_errors decorator in app/decorators.py. Read it and re-check.

Calibrate by treating findings as questions. A question costs seconds to answer and does not require you to be right about whether the model was.

Both are AI review. They are not redundant.

Terminal /reviewCopilot code review
RunsYour machine, before pushOn the pull request
SeesWorking tree, uncommitted workThe pull request diff
OutputTerminal, ephemeralReview comments, persistent
AudienceYouThe team
ConfigurationRepository instructions, skills, agentsInstructions, path-specific rules, skills
Can run commandsYesNo
RecordNoneIn the pull request

Two consequences worth acting on.

Terminal review catches things before they cost anybody attention. A leftover debug line found here never becomes a review comment.

It leaves no record. For anything the team should see — a concern about the approach, a known limitation — that belongs in the pull request description or a comment, not in a terminal session nobody else sees.

The productive sequence: terminal review to clean up, pull request review to catch what you missed, human review for judgement. Each is cheaper than the next.

/review on a path is useful beyond your own changes.

Before modifying unfamiliar code: “Review src/scheduler/ and tell me what it is responsible for, what calls it, and what would break if I changed the retry interval.”

Before a refactor: what the current structure does, and what depends on it.

When picking up somebody else’s branch: a read of what changed, before you start adding to it.

This is closer to chat’s explain use than to review, and it works well here because the agent can read whatever it needs rather than being handed files.

The caution is scope: reviewing a large directory produces a large amount of output, most of which is description. Narrow the question — “what would break if I changed X” beats “review this directory”.

Reviewing a range rather than the working tree

Section titled “Reviewing a range rather than the working tree”

Sometimes the thing to review is not what is uncommitted but a set of commits — a colleague’s branch, a range you are about to merge, or your own work after committing.

The agent can produce and read those diffs itself:

Review the changes on this branch against main. Use git diff main...HEAD. What would a reviewer ask?

Two details from AI-assisted Git code reviews that matter here:

Three dots, not two. main...HEAD compares against the merge base and is what a reviewer sees. main..HEAD compares tips and, on a branch that is behind, reports files as deleted that you never touched.

Commit-by-commit is a different question. Reviewing each commit separately answers “does each one stand on its own” — which is about bisectability rather than about the final state, and it is much easier to fix before pushing.

Show me each commit on this branch as a separate diff. Does each one leave the tests passing? Is any of them doing two unrelated things?

Reviewing somebody else’s branch this way is legitimate and has one caveat worth stating: findings from a terminal session are not review comments. If you have something to say about a colleague’s work, it belongs in the pull request where they will see it and where the conversation is recorded.

Terminal review reads the same repository configuration as everything else in the CLI, which means review criteria can be codified rather than retyped.

Repository instructions shape what it considers important.

Path-specific instructions apply where relevant, so a review of a migration checks migration things.

A review skill is the durable form for anything long — a full checklist with examples, loaded when relevant. See agent skills.

A review agent restricts tools so review is genuinely read-only:

.github/agents/reviewer.md
---
name: reviewer
description: Reviews changes against this repository's standards. Read-only.
tools: ["read", "search"]
---
Review the changes you are given against these criteria, in order:
1. Leftovers: debug output, commented code, hardcoded values, `.only` on tests.
2. Missing tests for new code paths.
3. Changes to defaults, guards or validation that the diff does not make obvious.
4. Input reaching a query, command or path without the helpers in `security/`.
5. New dependencies — name them and say what they are for.
For each finding: the file, the line, why it matters here, and how confident you
are. If a finding depends on code you have not read, say so.
Do not propose edits. Report only.

The last two instructions are the useful ones. Asking for a confidence statement makes triage faster. Asking it to flag when a finding depends on unread code surfaces the exact condition that produces false positives.

A specific case that is becoming common: reviewing work an agent just did, in the same session.

The obvious problem is that you are asking the thing that wrote the code whether the code is good, and it will find something positive to say. That framing produces confirmation rather than review.

Two ways to get useful output anyway.

Start a fresh session. A new session reviewing the diff has no memory of having written it and no investment in the approach. This is the closest thing to an independent read available, and it costs one command.

Use a review agent with explicit criteria. The agent above does not know or care who wrote the code; it checks the list. That consistency is the point — the same criteria applied regardless of provenance.

What neither fixes: an agent cannot tell you whether the approach was right. If it misunderstood the task, a review of the resulting code will find the code to be a good implementation of the misunderstanding. That judgement is yours, and it is why reading the plan matters more than reviewing the output.

The practical sequence for agent-produced work: read the plan before it acts, review the diff yourself, then run a fresh review session for the mechanical checks, then CI. Four layers, of which two are you.

The loop, compressed to what it actually looks like.

  1. git status — what is in the working tree at all. The agent can run this.

  2. /diff with a specific question — “what did I leave in?”

  3. Fix what it found, yourself where it is quick.

  4. Stage deliberately. git add -p if there is more than one logical change.

  5. /review on the staged diff — “what does this change that is not obvious?”

  6. Commit, with a message from the staged diff.

  7. Before pushing: /review the whole branch — “what would a reviewer ask?”

Steps 2 and 7 are the two that matter and take about a minute together. Step 7’s output is also the material for the pull request description, so it is not extra work.

The reason to put it in a numbered list rather than describing it as a habit: the friction that kills self-review is deciding to do it. A sequence attached to commands you already run does not require a decision.

Terminal review is individual by default, which limits it. Three things make it shared.

Commit the review agent. A .github/agents/reviewer.md with agreed criteria means everybody’s self-review checks the same things. That consistency is worth more than any individual’s thoroughness, and it is a reviewable artefact — adding a criterion is a pull request and a conversation.

Derive the criteria from real review comments. The best source is the last three months of pull request comments. What reviewers keep pointing out is exactly what self-review should catch, and moving it earlier is a direct saving in round trips.

Measure the round trips, not the findings. The signal that this is working is fewer review comments of the “you left a debug statement in” variety — the category that costs a cycle and teaches reviewers this branch needs careful reading. Finding count measures usage.

The failure to avoid: making it mandatory. A pre-push hook that runs a review and prints findings gets scrolled past, because output nobody asked for is output nobody reads. The habit you want is somebody choosing to look, which is a cultural thing rather than a technical one — and the way to get it is for the layer to be genuinely useful, which is what the criteria are for.

“Review this” with no specific question. A list of observations rather than findings.

Applying a suggested fix before evaluating the finding. Two judgements presented as one.

Arguing with a false positive instead of supplying the file. It could not see the caller.

Treating it as a substitute for tests. It has run nothing.

Reviewing only staged changes. The unstaged diff is where the accidental changes are.

Leaving concerns in the terminal. Anything the team should know belongs in the pull request.

Reviewing a whole directory. Narrow the question or drown in description.

Worth listing, because a layer that catches things starts to feel like a layer that catches everything.

Anything requiring execution. It has not run your code, your tests, or your linter — unless you ask it to, which is worth doing and is a different activity from reading.

Architectural fit. Whether this belongs in this module, whether the abstraction is right, whether this duplicates something elsewhere in the codebase it did not read.

Product correctness. Whether the behaviour is what was wanted. That is in the issue, the ticket, or somebody’s head.

Cross-cutting consequences. The effect of your change on a caller three modules away, on a downstream service, on a client that depends on the current response shape.

Anything about your organisation. That this table is replicated, that this endpoint has a partner contract, that this module is owned by another team.

Its own blind spots. It will not tell you it lacked context; it will produce a confident finding based on what it had.

That list is the specification for the layers after it. Tests catch execution. Code scanning catches data-flow problems this cannot see. Human review catches architecture, product and organisational knowledge — which is why it stays the accountable layer regardless of how good the earlier ones get.

Terminal review is a careful reader looking at your work before anybody else has to. It costs seconds, catches the things that would have cost a round trip, and proves nothing — which is what the tests are for.

  • /review and /diff operate on your working tree without you assembling anything
  • Removing the assembly friction is what makes self-review actually happen
  • Specific questions produce findings; “review this” produces observations
  • The agent can run commands to check its own findings, which chat cannot
  • Deciding a finding is real and evaluating its fix are two judgements
  • False positives almost always mean missing context, which is cheap to supply here
  • Terminal review leaves no record; anything the team needs belongs in the pull request
  • A read-only review agent with explicit criteria makes the layer consistent

Use a branch with real uncommitted changes.

  1. Run /diff with no further instruction. Count how many findings are actionable.

  2. Ask three specific questions instead. Compare the actionable proportion.

  3. Ask “for each new code path, is there a test?” Predict: does it find a gap you knew about?

  4. Find a false positive. Tell the agent which file it was missing and ask it to re-check. Predict: does the finding survive?

  5. Accept a suggested fix without evaluating the finding first. Predict: was the change necessary?

  6. Write a review agent with explicit criteria and run it. Compare consistency with the ad-hoc review.

  7. Push the branch and let Copilot code review run. Predict: does it find anything the terminal review did not?

AI-assisted engineering learning pathEleven lessons on getting value from Copilot and agents without giving up review discipline.