Workflows Make it check its own work
It is never the code
Coding agents are like actual coders. Ask one whether its work is right and it says yes. It wrote the code, then wrote the tests, then ran them, then told you they passed. All three of those came out of the same head. This is the hook that stops a session finishing until somebody who was not in that head has read the code.
- What it stops
- A session shipping code nobody else read
- Cost
- $0. One Node script, no dependencies
- Time to wire up
- About 5 minutes, once
- How often it interrupts
- Once per session. Never twice
The loop that cannot see itself
Here is the failure, in order. The agent reads your request and forms a belief about what you want. The belief is slightly wrong. It writes code that expresses the belief. Then it writes tests, and the tests assert the belief, because that is where the tests came from. Then it runs them. They pass. It tells you it is done, and it is not lying.
Nothing inside that sequence can catch the error, because every step was produced by the thing that is wrong. The tests are not evidence of correctness. They are a second copy of the same idea, written in a different file.
This is also why "review your work carefully before you finish" does almost nothing. You are asking the same session, holding the same belief, to notice that the belief is wrong. It will re-read the code, find that the code matches its understanding, and confirm. That is not a review. It is the same answer, typed twice.
The fix is not a better prompt. It is a different reader.
The check has to happen somewhere the belief is not.
So: a second agent, opened fresh, with none of the conversation that built the thing. It gets the diff, the project's conventions, and one paragraph saying what the change was supposed to do. It does not get the reasoning, the false starts, or any of the explaining. It has no idea why anything was done that way, and that is the entire point, because it will read what is actually there instead of what was meant.
Three details make the difference between that and theatre:
- It must be a separate agent, not a fresh instruction. If the build conversation is still in the context, this is self-review wearing a costume.
- It works out the right answer from the source, not from the code. Reading the code to see what it does and then agreeing that it does that is not a review of anything.
- It forms its own view before it reads the test file. Order matters more than people expect. Expected values seen first become an anchor, and a reviewer that has seen them will reason its way toward them.
A rule in a document is a rule nothing follows
I had all of that written down for months. It worked when I remembered it, which was most times, and then a busy session would ship something on its own say-so and nobody would know until later.
That is the normal fate of a written rule. It is invisible to everything except a session that happens to read it that day. If you want a habit, you have to attach it to something that runs.
Claude Code will run a script of yours at defined moments. One of those moments is the session trying to end, which is exactly when an unreviewed change is about to become somebody's problem.
What the hook actually does
| Step | What happens |
|---|---|
| Reads the transcript | The session's own log records every file written and every helper agent sent out. The hook reads it rather than watching the file system, so it knows what was written and in which order. |
| Collects the code | Every code file this session wrote or edited. Prose, config and data are skipped on purpose. |
| Clears what was reviewed | When a helper agent comes back, the files that existed when it was sent are marked as read. Code written afterwards is not covered by it, and needs another look. |
| Blocks once | If anything is left, the session cannot finish. It gets the file list and the review prompt to paste into a fresh agent. |
One thing it deliberately does not do is take the dispatch as proof. Helper agents run in the background, so the moment one is sent is not the moment it has read anything. A hook that counted the sending would let a session fire off a reviewer and finish in the same breath with nobody having looked at a single line. So it waits for the answer to come back.
A hook cannot open an agent itself. It is a shell command, not a session, so it has no way to spawn one. What it can do is refuse to let the session finish until the session opens one, which arrives at the same place by a shorter road.
The message carries the prompt
"Get this reviewed" produces a rubber stamp. Most of what the hook prints is the actual prompt, because the wording is doing the work: review a change you did not write, trace one real input by hand, do not read the test file until you have formed your own view, then ask whether those tests would pass even if the code were wrong in the way you were worried about.
That last question is the one that finds things. A test that passes either way is not coverage, and it is the single most common thing an agent produces when it writes tests for its own code.
It also says, in plain words, do not review it yourself.
It gives up on purpose
Once per session, and then it lets go for good.
The first version used the flag that stops a hook firing twice in a row, which sounds like the same thing and is not. That flag resets on your next message, and "code written, nobody reviewed it" stays true for as long as you keep building, so a six-turn session got interrupted six times. A check that will not release is a check you delete. It now writes down that it has fired and stays quiet for the rest of the session.
If the session finishes anyway, it has to say in its reply that the code went out unreviewed. That turns a shortcut into a decision on the record rather than a silence.
Two things it cannot see
Worth knowing before you install it, because a check you trust further than it deserves is worse than no check.
- Files written by a shell command are invisible. Something built by
cat > file.js, an in-place edit, or a code generator never shows up as a file write in the log, so the hook does not know about it. - It cannot tell a review from any other helper. An agent sent out to search your codebase looks identical to one sent out to review it, and both satisfy the check. Reading the instructions to guess at intent would trade a known limit for a guess.
Both of those fail quietly rather than falsely, which is the right direction for something that can block you. Both are written into the file as known limits rather than left as a surprise.
Take it
Public, MIT licensed, plain Node with nothing to install. It ships with its own test file, because a script that can refuse to let you finish gets proven before it goes anywhere near a real session.
- review-check.mjs The hook. The comment at the top explains why it exists and the two things it cannot see.
- The review prompts The full versions, including a second one for anything whose right answer lives in a published table or standard.
- The event that fires when a session ends Where this gets wired in, what it is handed, and how blocking works.
- The other check on the same event The one that stops a session ending with finished work saved on one machine.
Why this shape
The obvious version of this is a rule in your instructions file saying to get a review. I had that. It is free, it is one line, and it works right up until the session that most needs it is the one that skips it.
The other obvious version is more tests. That is the trap the whole thing is about: more tests written by the same author produce more copies of the same belief, and a longer green run to feel good about.
What is left is small. Take the rule you already wrote and attach it to the one moment it gets skipped.
Hooks live in your settings file
This gets wired up in the same file that decides what Claude can do without asking you first. The free settings builder writes that file from a few plain questions, with nothing to install.
Build your settings file →