cr — cross-session code review
One agent session reviews another session's open PRs and sends findings back
over SendMessage. The reviewer never edits, commits or merges.
Why a separate session rather than a subagent. A review's value comes from not sharing the author's priors. A forked subagent inherits the framing; a briefed one receives the framing in its prompt. Both read past what the author read past. The findings that justify this workflow are rarely "line N is wrong" — they are "this document states two contradictory things", "this claim is derived but written as measured", "this rule invalidates a thing it depends on". Those are failures of the artifact as read by someone who was not there, and shared context is exactly what hides them.
The cost is real and is the reason for the rules below: addressing is manual, there is no automatic handoff, and the peer's state is not observable.
Invocation
/cr <session-name> # review the PRs that session opened
/cr <n> [n...] # review these PRs; resolve the author session to reply to
/cr # this repo's open PRs authored by the current user
All three resolve to the same state: a (PR set, author session) pair.
Resolve it with the recipes below — never by guessing from a session's
busy/idle status. Guessing misroutes; it has.
Resolving a session name to a session id
python3 -c "
import json, glob
for p in glob.glob('\$HOME/.claude/sessions/*.json'):
try: d = json.load(open(p))
except Exception: continue
print('%-26s %-38s %s' % (d.get('name'), d.get('sessionId'), d.get('cwd')))
" | sort
The [ref] shown by ListAgents is not a prefix of the session id; the two
are unrelated. This table is the only reliable map.
Resolving a PR to the session that opened it
grep -l 'pull/<N>' ~/.claude/projects/<project-dir>/*.jsonl
Then map the returned session id back through the table above.
Resolving the host and repo
Derive both at runtime — never hardcode them:
gh auth status # which hosts gh is authenticated to
git remote -v # this repo's slug and host
For a GitHub Enterprise host, prefix commands with GH_HOST=<host>.
gh pr and gh issue do not honour --hostname; only gh api does.
One pass
Send before watching. If you hold a finding that has not been delivered, send it first. Watching a session that does not know what to change is a self-inflicted deadlock, not waiting. This was measured: two consecutive no-op ticks were caused by holding a finding and watching anyway.
Read the state, not the summary. For each PR record
state,mergeStateStatusandheadRefOid. A peer's own report — including an automated idle notice — is a claim to verify, never evidence.Diff only the increment, from the SHA recorded in the previous pass. Carry the baseline SHA table forward explicitly; it is the only state this workflow keeps.
Verify the peer's citations before accepting them — including in entries that are themselves rules about citation. A
§Nor line reference that resolves is not one that supports the claim; checkers verify the first. Confirm load-bearing facts against the code, the config that actually deploys, and the tracker — not against the PR body.Report what you did not verify. Every pass states it: tests not run, checkers not run, which numbers are the author's measurements you cannot reproduce. A review that hides its own gaps is the defect it exists to find.
Send the findings with
SendMessage. Say plainly when something does not block merge.
Calibration
- Do not manufacture findings to keep the pass alive. State the terminal state plainly when you reach it.
- When the peer pushes back on your framing, evaluate it on merit. It will sometimes be right, including about the exact error class you have been raising against it. That is the workflow working, not failing.
- Never merge, never edit, never commit. Merging is a human decision. If a
merge command is blocked in your environment, report
mergeStateStatusand the exact command and hand back — that is a terminal state, not unfinished work.
Running until convergence
/cr performs one pass. To run until the review converges:
/loop /cr <session-name>
/loop's dynamic mode already provides pacing, event wake-up and noop folding;
do not reimplement scheduling inside this skill.
Waking on the peer instead of polling
SendMessage with notify_when_idle: true (no message) subscribes to one
notice when the peer next goes idle. It costs the peer nothing.
🔴 Arm it only when ListAgents shows the peer BUSY. Subscribing while it
is already idle fires immediately, which turns the loop into a tight spin.
Measured.
The subscription is one-shot: re-arm it each time it fires, subject to the BUSY rule. It cannot see a human merging, so keep a long fallback heartbeat (1200–1800s) for anything the peer does not cause.
Stop condition
Stop when the peer pushes a change and the pass finds nothing new in it — one clean pass over a real increment.
Do not stop merely because you currently hold nothing: that stops at the first quiet moment. Measured — a pass declared terminal was followed by two further passes that each found a real defect.
Merging is not the stop condition. The human merges; the review converges independently.
What does not belong in this skill
A checklist of what counts as a finding. That judgement must stay with the model. A checklist narrows the population an assertion covers, which is the failure mode this workflow most often catches in others. The most valuable findings in the run that produced this skill would not have appeared on any checklist written in advance.