web-review
Open a human code review of your own changes and act on the result.
By default you do not review the code. You describe what you changed, open the page, and hand it to the human — the comments are theirs to write. Only annotate lines yourself when the user explicitly asked you to review your work before opening (see Reviewing your own work first).
Steps
Write
<git-dir>/web-review/request.json, where<git-dir>is whatgit rev-parse --absolute-git-dirprints. Resolve it; do not assume.git/, which in a worktree is a file pointing elsewhere, so the literal path.git/web-review/can neither be read nor created:mkdir -p "$(git rev-parse --absolute-git-dir)/web-review"{ "summary": "Markdown. What changed and why." }A summary is all you write by default. No
annotationskey.Open the review, from the repository root.
--timeout 0looks for a result once and returns straight away instead of blocking, so you can show the human the address:node <skill path>/dist/web-review.mjs --timeout 0It opens the browser itself and prints a
pendingresult carrying the address:{ "status": "pending", "url": "http://127.0.0.1:53411/?t=8f2c…" }Any other status here is a real outcome — read it and stop; do not go on to step 3.
Tell the user, in your reply text where they can actually see it — not buried in a tool call — the URL and that you are blocked:
Review open: http://127.0.0.1:53411/?t=8f2c… I opened it in your browser; if nothing came up, use the link above. Waiting for you to submit the review before I continue.
Print the
urlexactly as it came back: the token in it is what authenticates the page.Do not end your turn here. Telling the human something reads like the end of a turn, and it is not one: step 4 belongs to this same turn, right after this text. That is the only reason step 2 used
--timeout 0— to hand you the address so you could show it and then go and wait. Ending the turn on this step is how a submitted review ends up sitting on the server unread while you do other things.Wait for the submission, from the same repository:
node <skill path>/dist/web-review.mjsDo not write
request.jsonagain — the round is already open and your summary is already in it.Read the JSON printed between
<<<WEB_REVIEW_RESULTandWEB_REVIEW_RESULT>>>.
While a round is open
Do not commit, push, or move on to other work of your own accord. You are waiting on the human.
The human, though, will interrupt you — that is normal, they are reading a diff and thinking out loud, and what they ask for may well be a change to the very code under review. Do what they ask. But a round you left open is still open, and nothing else will remind you:
Before you answer any request that arrives while a round is open, ask whether the review has landed:
node <skill path>/dist/web-review.mjs --timeout 0--timeout 0looks once and returns straight away, so this costs one call and no waiting — unlike step 4's command, which blocks for nine minutes.pendingmeans the human is still reading. Carry on with what they asked, and repeat the address and the "waiting" line at the end of your reply so the link stays in view.submittedmeans the round is over. Act on the verdict, and say that the review predates whatever you have changed since it was opened.Never take silence for an answer. A submission made while nothing was waiting is held on disk indefinitely and handed to the next run, so "the human has not submitted yet" is something you learn from a
pendingand from nothing else. Not having seen a result is not evidence that there isn't one.
What each status means
pending— no submission yet. In step 2 that is the normal case and means the page is up. In step 4 it means the reviewer is still reading: run the command again, from the same repository, exactly as before. It is not a result: do not treat it as approval and do not start editing. The review server keeps running in the background even after this command exits, so re-running reattaches to it instead of starting over. If the human submits after this command has already exited, the submission is held for you: the next run returns it, however long you wait before running it again. Repeat the address and the "waiting" line to the user each time you re-run, so they keep the link in view.no_changes— there was nothing to review.aborted— the review was cancelled, or the server was stopped. This is not approval.error— readmessage.submitted— a round finished. Readverdict:approve— stop. Do not commit, push, or open a pull request unless the user asks separately.request_changesorcomment— act onthreads, below.
Acting on request_changes or comment
Evaluate each comment on its merits. Apply what is right. Where you disagree, say so instead of complying silently: put your reasoning in a reply and let the human decide.
{
"replies": [
{ "threadId": "t3",
"body": "Kept the inline version: extracting it would need the request context, which the hook cannot reach." }
]
}
Write that to the same request.json as in step 1, together with a summary
of what you changed, then run steps 2–5 again to open the next round.
Threads carry across rounds, so the human sees your reply next to their
original comment.
Threads with "status": "outdated" were anchored to lines that no longer exist.
Read them for intent; do not try to apply them literally.
A thread you saw in an earlier round can be missing from threads entirely: the
human can delete any comment in the page, yours included. Treat it as withdrawn
and move on — do not raise it again.
Reviewing your own work first
Only when the user explicitly asks for it — "review your changes before opening it", "flag anything you're unsure about", "do a pass first". Otherwise skip this section entirely.
Get the changed files and their line numbers in one call, before step 1, from the repository root:
node <skill path>/dist/web-review.mjs --prepare
It prints the diff with the line number each line has on each side:
old new
== src/auth.ts modified +1 -1
@@ -85,5 +85,5 @@
85 85 const user = req.user;
86 86 const token = sign(user);
87 87 audit(user, token);
88 . - await wait(500);
. 88 + await wait(delay);
89 89 return token;
Read the number off the column matching the side you want to annotate:
the new column for side: "new", the old column for side: "old".
Do not count lines yourself, and do not grep for them.
This is read-only: it opens no review and can be run at any time. A file
that is binary or very large is shown as a header only, and says so —
read that one yourself if you need it. A file well within that limit can
still come out as a header only, marked — omitted, does not fit the remaining output budget, once the total output grows too large; a
bodyless header does not by itself mean the file is unchanged, so check
the note next to it. A renamed file is headed by its new path
(== src/new.ts renamed from src/old.ts) — always put that new path in
file, on both sides; the old path still anchors the comment, but the
browser keys threads by the new path and filters out anything else, so the
comment would silently never appear.
Then add an annotations array to the request.json of step 1:
{
"summary": "Markdown. What changed and why.",
"annotations": [
{ "file": "src/auth.ts", "line": 88, "side": "new",
"body": "Hard-coded delay; I found no existing config. OK?" }
]
}
Annotations are for genuine uncertainty — hesitations, assumed debt, things
worth a second pair of eyes. Do not annotate lines you are confident about.
Take every line from the columns printed by --prepare; a line number that
does not exist is an error, not a near miss.