9. A Second Pair of Eyes That Cannot Edit
Summary
A reviewer that checks a change against its issue
A subagent is a helper the main Claude Code session hands a task to. It has its own context window, system prompt and tool list, and it returns only a summary. Because it starts with almost no knowledge, the main session must pass along what it needs. Custom subagents read CLAUDE.md; the built-in Explore and Plan do not.
The worked example is a new request in the shared-list app: show an item count in the list header (issue thirteen). Its two acceptance criteria are that the header shows the count, and that the count stays correct after add, delete and reload. The reviewer, criteria-reviewer, is a Markdown file in the project's .claude/agents folder. Its frontmatter holds a name, a description, a tools line limited to Read, Grep and Glob, and an optional model. The tools line is enforced by the program, unlike an instruction in the body. Leaving it out gives the subagent every tool the main session has. Since the reviewer has no Bash or MCP tools, the main session fetches the issue and runs git diff, then passes both in the handoff.
The reviewer is tested on a correct change and on a deliberately broken one where the delete handler never updates the count. It should pass the first and fail the second. Its report is a lead, not proof: it read code and did not run the app, so the type check, regression tests and a manual click-through still apply.
Whether the reviewer earns its place
The decision comes from the evals baseline: the fast-add bug and the done-flag feature, each run twice with the reviewer line in CLAUDE.md and twice without. Four runs per task is a small sample, so one caught mistake is a hint. The suggested outcome is to keep the reviewer for issue-linked changes, especially database ones.
Three failure modes are covered: a vague description, a handoff with a diff but no criteria (reports that all say "looks good"), and splitting one task across many helpers that share state.
Calling the reviewer by name
To make the review happen for certain, name the subagent in the request or pick it with the at sign. A reusable request reads the issue, runs git diff against main, sends the criteria word for word plus the diff to criteria-reviewer, and lists each verdict with evidence before changing anything. Run it on the broken count change and check with slash context that the main session's context barely grew.
A reviewer that checks a change against its issue
The agent that writes a change is also the one most likely to grade it generously. It knows what it meant to do, so it reads its own diff in that light. So far in this course you have filled that gap yourself. You read every diff and clicked through every journey, and the type check hook and the regression tests catch what they were built to catch. This chapter adds one more check. A second, separate helper reads the change against the acceptance criteria in its GitHub issue and reports whether each criterion is met. That helper is a subagent. By the end of the chapter you will know how to build one, how to test it, and how to decide from your evals baseline whether it is worth keeping. This is the last tool in the stage where we add power to a single working session. After it, the work starts to leave the laptop.
Start with what a subagent is in Claude Code. Your main session is the conversation in the terminal where you type requests. It has one context window, the running record of everything it has read and said. A subagent is a helper the main session can hand a task to. It gets a fresh context window of its own, a system prompt of its own, and its own list of tools. It does its work in that separate space and sends back one thing: a summary of what it found. The main session never sees the files the subagent opened or the searches it ran. It sees the report.
That separation helps and it costs, for the same reason. It helps because a review means reading many files. If the main session did all that reading itself, those file contents would fill its context and push out the plan you are working from. With a subagent, the reading happens somewhere else and only the conclusion comes back. It costs because the subagent starts knowing almost nothing. It does not see your conversation. It does not know which issue you are working on unless the main session tells it in the handoff. Whatever the subagent needs, it has to be given or has to be able to find.
Claude Code ships with some built-in subagents, Explore and Plan among them, and you can write your own. You met an important difference between the two kinds earlier in the course. The built-in Explore and Plan skip reading CLAUDE.md to save time and context. A custom subagent that you define does read your project's CLAUDE.md. That matters here, because your CLAUDE.md holds the definition of done and the constraints on where validation lives and how the database is reached. A reviewer that reads those rules can check the change against them too.
One more thing a subagent is not. Later, when the course reaches GitHub, you will run several sessions at once in separate Git worktrees, each working its own task on its own branch. Those are parallel sessions. Each is a full Claude Code session with its own files on disk and its own conversation. A subagent lives inside one session, works on one piece of the current task, and reports back to that session. Today there is one session, one change, and one helper.
Now the worked example. Say the shared-list app has a small new request: show how many items a list has. You write it up as a GitHub issue the same way you wrote issue twelve. Call it issue thirteen. The problem statement says that people with long lists cannot tell at a glance how much is on them. The acceptance criteria are two testable sentences. First, the list header shows the number of items in the list. Second, the number stays correct after adding an item, after deleting an item, and after a reload. Those criteria are what the reviewer will check against, so they need to be specific. "Shows a count" would let almost anything pass. "Stays correct after add, delete and reload" names three moments someone can check.
There are two ways to create a subagent. You can type slash agents in a Claude Code session, which opens a manager that walks you through creating one and saving it. Or you can write the file yourself. Either way you end up with the same thing: a Markdown file in a folder called agents inside your project's .claude folder. Because it lives in the project, it gets committed and anyone who clones the repository has it. A subagent saved in your home directory's .claude folder would be personal and follow you across projects. This one belongs to this app, so it goes in the project.
The file starts with frontmatter, a short block of settings at the top, much like the skill you built for reproducing bugs. It has four entries.
The first is the name. Call it criteria-reviewer. The name is how you and the main session refer to it.
The second is the description. As with a skill, the description is what the main session reads when it decides whether to hand a task to this helper. So it says when to use it and when not to. Something like: use after a change is implemented and before it is committed, to check the diff against a GitHub issue's acceptance criteria and report pass, fail or unclear for each one. Do not use it for writing code, planning or bug reproduction.
The third is the tools line. Here it says Read, Grep and Glob, and nothing else. Read opens a file. Grep searches file contents for text. Glob finds files by name pattern. With only those three, the subagent can look at anything in the repository and change nothing.
The fourth, which is optional, is the model. You can name sonnet, opus or haiku, or say inherit to use whatever the main session runs. Your project default is already Sonnet at medium effort, so you can leave this out or set inherit. A review that reads a database change could warrant Opus, which is the same reasoning your CLAUDE.md already applies to database work.
Pause on the tools line, because it is the reason this design holds together. The body of the file will tell the reviewer not to edit anything. That sentence is an instruction, and the model will usually follow it. But the tools line is a limit the program enforces. A subagent that has no Edit, Write or Bash tool cannot change a file however its instructions read and whatever text it meets in the code. That is the same split this course keeps returning to: a rule you ask the agent to follow, versus a limit the tool actually enforces. One detail makes the split sharper. If you leave the tools line out entirely, the subagent inherits every tool the main session has, which includes editing, the shell and your GitHub MCP tools. So for a reviewer, leaving the line out is the risky choice, not a neutral one.
The restricted tool list has a consequence you need to plan for. Without Bash, the reviewer cannot run git diff. Without the MCP tools, it cannot fetch issue thirteen. So the main session does those two jobs and puts the results in the handoff. That works well in practice. The main session already reads issues through the GitHub connection you set up, and it can run git diff in the shell. The handoff gives the reviewer the criteria word for word and the diff, and the reviewer uses Read and Grep to look at the surrounding code the diff touches.
Below the frontmatter is the body, which becomes the subagent's system prompt. It does four things in plain sentences. It tells the reviewer that it will receive an issue's acceptance criteria and a diff. It tells it to take each criterion in turn and decide pass, fail or unclear. For each verdict it must give evidence: the file and line that meets the criterion, or the place where something is missing. It says unclear is the right answer when the code alone cannot settle the question, for example when a criterion depends on how the page behaves after a reload and nothing in the code clearly decides it. And it tells the reviewer not to propose rewrites or style changes, only to report against the criteria. That last rule keeps the report short and keeps the reviewer from drifting into a second round of design.
Once the file exists, restart the session or open slash agents and confirm criteria-reviewer is listed as a project agent. Then test it small, the way you tested the GitHub connection with one read before trusting it.
The first test is a change you know is good. Let the main session implement issue thirteen in the usual way: inspect, state the behavior, make the change, read the diff. Say the change adds a count to the list header, worked out from the same list of items the page displays, so adding, deleting or reloading all update it. Before you commit, ask the main session to send the issue's criteria and the diff to the criteria-reviewer subagent. You can say the name plainly in your request. The main session hands over the task, a line in the terminal shows the subagent working, and a few moments later the report comes back. In this illustration it says criterion one passes, pointing to the header component and the line that renders the count. It says criterion two passes, because the count is derived from the displayed list rather than stored separately. It flags reload as covered by the existing loading code and names that file.
The second test is a change you know is incomplete. Undo the good version, and have the main session write one that is wrong on purpose. Say it keeps a separate count number in the page's state. The add handler increases it, and a reload recalculates it, but the delete handler never touches it. Send that diff to the reviewer. A useful report says criterion one passes and criterion two fails, because the delete handler changes the list without changing the count, and it names the delete handler's file and line. If the report says everything passes, the reviewer is not doing its job. The fault is almost always in the body or the handoff, not the model. Check that the criteria actually arrived in the handoff and that the body asks for evidence on each one.
Both tests matter for different reasons. The good change shows the reviewer does not invent problems. The broken one shows it can find a real one. A reviewer that passes only one of the two tests is either noise or a rubber stamp.
When the reviewer reports a failure on real work, the main session fixes from the finding. That fix still goes through the routine you already have. The type check hook runs after the edit. A regression test for the delete case should fail on the broken version and pass on the fixed one. The full suite runs. Then you click the journey yourself: add an item, delete one, reload, and watch the number each time. The reviewer's report is a lead, not the result. It read code. It did not run the app. A passing review is not a working user journey any more than a passing test suite was, back when the pasted-name bug passed its tests and still failed in the browser.
When the change is done, commit it as usual, and commit the agent file as its own small commit with a message that says what it adds. Then add one line to CLAUDE.md. It says that before a change tied to an issue is committed, the criteria-reviewer subagent checks the diff against the issue's acceptance criteria, and that its findings are fixed or answered before the commit. Because custom subagents read CLAUDE.md, the reviewer sees that line too. That does no harm and keeps the record consistent.
Whether the reviewer earns its place
So far you know the reviewer can catch one planted gap. That is not the same as knowing it makes your work better. It adds a step to every change. It uses tokens. And its reports take you time to read, especially when they flag something that turns out to be fine. The question is the one the evals baseline was built to answer: does this change to your setup lower the total cost of finished work, counting your review and repair time, without making anything worse?
Use two of the three baseline tasks. The fast-add bug is one, because it has crisp criteria from issue twelve. The done-flag feature is the other, because that is where a run once passed the suite while the clicked journey failed. Replay each task the way the baseline says: a clean worktree from the recorded starting commit, today's CLAUDE.md and .claude folder copied in, and the evals folder removed so the agent cannot see the hidden checks. Run each task twice with the reviewer line in CLAUDE.md and twice without it. That is eight runs. For each one, record whether the finished change passes the deciding check and the hidden checks, how many minutes you spent reviewing and repairing, and the cost from slash cost.
Here is how that might come out. These numbers are a supposition, not a measurement. On the fast-add bug, all four runs finish correctly. The runs with the reviewer cost a little more in tokens, and each adds about a minute of your time reading a report that says everything passes. On that task the reviewer adds cost and catches nothing. On the done-flag task, say one run without the reviewer repeats the old problem. The checkbox shows ticked on screen, but the database client never writes the done column, so the tick disappears on reload. You find it by clicking, and it takes about fifteen minutes to trace and fix, as it did last time. In one run with the reviewer, the same mistake appears, but the report marks the criterion about surviving a reload as failed. It points out that the database function the interface calls never mentions the done column. The main session fixes it before you look, and your repair time drops to a couple of minutes of checking.
Read that result carefully. It does not say the reviewer is good. It says the reviewer paid for itself on a change where the criteria could be checked by reading code, and was pure overhead on one where the existing tests already settled things. Four runs per task is still a small sample. One caught mistake is a hint, not proof. A reasonable decision from this supposition is to keep the reviewer for changes tied to an issue with acceptance criteria, especially database changes. You would not run it on every small edit. You would record the eight runs in the evals results file and commit them, so the next change to your setup is compared against this one too.
If the numbers had gone the other way, the decision changes with them. Suppose the reviewer flagged things that turned out fine in half its reports. Then you would be spending minutes on noise, and you would either tighten its body or remove it. Neither result is a failure of the method. The method is that the baseline decides.
Three failure modes are worth recognizing, because each looks like success at first.
The first is a vague description. If it says something like "helps with code quality", the main session either never hands it work, because nothing clearly matches, or hands it everything, including tasks it has no criteria for. You saw the same thing with the reproduce-bug skill, and the fix is the same: reword the description so it names the moment of use and what it excludes, then test that it gets called on a finished change and not on a planning request.
The second is a reviewer with nothing to review against. If the handoff carries only the diff, the reviewer has no criteria. It falls back on general impressions and tends to approve, because most diffs look reasonable in isolation. A string of reports that all say looks good is the sign. The cure is upstream: the issue needs testable criteria, and the handoff needs to carry them word for word.
The third is splitting one task across many helpers. It is tempting to add a subagent for the database, another for the interface and a third for tests. But those pieces share state. The interface depends on what the database function returns, and the tests depend on both. Each subagent sees only its own handoff, so none of them holds the whole picture, and the main session ends up stitching together reports that disagree. The reviewer works because its job is separate from the writing, limited to reading, and checkable against a fixed list. A helper that needs to know what another helper is doing does not meet that bar.
With the reviewer in place, a single session in this repository can now take an issue, reproduce and trace when needed, implement a change, have it checked against its criteria by a helper that cannot edit, verify the journey and commit. All of that still happens on your laptop with you at the keyboard. The next step is to move a modest job onto a machine that keeps working when the laptop is closed, and to make sure it reports when it finishes and can be picked up again if it stops halfway.
Before that, one practical shortcut that builds directly on the reviewer.
Calling the reviewer by name
You can let the main session decide when to use criteria-reviewer, based on its description and the line in CLAUDE.md. That works, but it puts the choice in the model's hands, and on some changes it may skip the step. When you want the review to happen for certain, call the subagent directly in your request.
There are two ways. The plain way is to name it in a sentence: ask the main session to use the criteria-reviewer subagent on the current diff against issue thirteen. Naming it is a strong signal, and in practice the main session follows it. The more direct way is to type the at sign in the prompt and pick the agent from the list that appears, much as you would mention a file. That mention points the request at that agent, so you are not relying on the model to infer it.
Here is a request worth reusing as written, with only the issue number changing. Read issue thirteen through the GitHub connection. Run git diff against the main branch. Send the issue's acceptance criteria word for word, plus the diff, to criteria-reviewer. Then list each criterion with the reviewer's verdict and evidence before changing anything. That last clause matters. It makes the main session show you the report before it starts fixing, so you can judge each finding instead of finding out afterward that the code moved.
To check the shortcut works, run it on the deliberately broken count change from earlier. Two things should happen. The terminal should show criteria-reviewer starting, rather than the main session doing the reading itself. You can confirm the reading happened elsewhere with slash context, which should show the main session's context barely grew, even though the reviewer opened several files. And the report should name the delete handler as the failing spot. If the main session reads the files itself instead, the handoff did not happen, and the review you got is the author grading its own work.
The saving here is concrete. A review of a change touching four or five files keeps all that reading out of your main session, so a long task keeps its plan and decisions in view for longer before you need to hand off and clear. You also stop typing out instructions for a review by hand each time. The method lives in the agent file, the trigger lives in one sentence, and the result can be checked against a change you already know is broken.
