Skip to content
xiv

pr-review-loop

Open a PR (or attach to an existing one), then settle it against the two signals only GitHub can supply — CI status and comments from human reviewers — in a small, bounded number of rounds. It never merges, never posts comments, and never waits on a review bot.

  • Triggered by: xiv pr refine [prNumber] (alias prl) — see /commands/pr
  • Also runs as: the tail subflow of linear-to-pr (xiv ship)
  • Source: pack/workflows/pr-review-loop.tsx

Task graph

Loading diagram...

Open vs. attach

The branch splits on whether the caller supplied a PR number:

  • No prNumber (the xiv ship path, or xiv pr refine --branch <name>): describe reads the branch's diff against the base and writes a structured description (Summary / Test Plan / Known gaps — the prDescriptionSchema), then open-pr creates the PR with that body plus a deterministic footer (Linear issue link). describe is continueOnFail: if description generation breaks, the PR still opens with a plain template body — a thin PR beats no PR.
  • prNumber supplied (xiv pr refine 1234, or the current branch's PR): describe and open-pr are skipped and attach-pr does a read-only gh pr view to confirm the PR exists and is open, capturing its URL and head SHA. It never creates a PR or pushes.

The loop: signals → fix

Each round takes one snapshot and acts on it — the signals step is explicitly not a poll; nothing sleeps or waits on a remote reviewer.

  • rev:signals reads all four comment surfaces (gh pr checks, formal reviews, inline diff comments, issue/conversation comments), classifies CI as passing / failing / pending / none, and emits findings: one per failing check (source: "ci", quoting the actual failure) and one per unresolved human comment (source: "human"). Bot comments are ignored entirely; "unresolved" is judged against the actual code at the head, not GitHub's thread flags. It sets clean: true only when CI is passing-or-none and there are no findings.
  • rev:fix addresses each finding properly (root cause, not check-loosening; meaningful tests for behavior comments), pushes, and reports addressed[] / skipped[] (skips carry an honest, human-readable reason). It is skipped when there is genuinely nothing to fix: no snapshot yet, everything already clean, or checks still pending with nothing red and no comment to answer — clean: false alone is not a reason to edit code.

Exit conditions and cap

The loop exits when the latest snapshot reports clean: true. The cap is maxRounds, default 2, and hitting it returns the last state rather than failing (onMaxReached="return-last").

The cap is deliberately low: code review already happened locally before the PR existed (see /concepts/review-model), so the only things left to chase here are a red build and a human comment. Two rounds fixes a failure and confirms the fix; anything beyond that is a person's call, not a loop's.

After the loop, rev:report states the final position: either "green and no outstanding comment — ready for a human to review and merge", or exactly what is still open and who needs to act.

Inputs and outputs

Input:

FieldDefaultMeaning
prNumber(optional)Attach to this existing PR instead of opening one.
branch""Branch to open a PR from (falls back to the current branch).
base"main"PR base branch.
issueContext(optional){ key, title, url, acceptanceCriteria[] } — used for the title, body, and footer.
knownGaps[][]Findings the implement loop shipped without fixing, forwarded by linear-to-pr so the PR names its own limitations. Empty when run directly against a branch.
maxRounds2Loop cap.

Output (the table named output, consumed by linear-to-pr):

FieldMeaning
prUrlThe PR settled or opened.
resolvedTrue only when clean at exit.
pending[]Outstanding items for a human.
summaryFinal state in prose.

Models and failure semantics

Every task here runs on the autonomous agent pool — the heavy Claude model with git/gh permissions and a hard never-merge system prompt — except rev:fix, which runs on the smart pool (heavy Claude, tiered by XIV_TIER). Under XIV_ENGINE=codex everything moves to the codex agent. See /concepts/model-tiers.

  • describe is the only continueOnFail task; its fallback is the plain template body.
  • attach-pr failing (PR closed or missing) fails the run rather than opening a duplicate.
  • Task timeouts: 15 minutes for describe/open, 10 for signals, 30 for fix, all with heartbeats.