Skip to content

Code review

Two things about review here are not the same as on other forges: pull requests stack, and review threads survive a force-push. Both fall out of the engine underneath rather than being features bolted onto it — see The storage engine.

A pull request can name another pull request as its parent. That makes a stack: each PR targets the branch of the one below it, and the bottom of the stack targets the default branch.

main ← A ← B ← C
↑ ↑
bottom top

The point is that each PR in the stack is reviewed and approved on its own, against only its own diff. You do not have to hold “the parts of this 900-line diff that belong to the refactor” in your head while reviewing the feature that sits on top of it.

Stacks are linear — one child per parent — and bounded at 32 deep.

This is the part that is usually painful elsewhere. When a PR in a stack merges, its children are re-parented onto its parent, and each child’s base branch is retargeted onto the branch the merged PR was targeting.

Both halves move together, which is what makes the stack stay correct:

  • The pointer moves — a 3-PR stack A → B → C collapses to A → C when B merges.
  • The base ref moves — C is retargeted onto whatever B was targeting.

Without the second half a child would keep targeting a source branch that never advances again, and its mergeability would answer the wrong question forever. Because the bottom PR targets the default branch, merging the bottom hands the default branch straight to its child, and the stack simply shortens.

A review thread is anchored to a file, a line, and a commit. When the PR’s source branch advances — new commits, an amend, a force-push — every open thread is re-aligned against the new head rather than being orphaned.

The alignment is per-thread, and it is decided by reading the file rather than by trusting line numbers:

At the new head Outcome
File is gone thread marked outdated
File is byte-identical anchor advances, line unchanged
File changed, the line’s text survives anchor and line number advance
File changed, the line’s text is gone thread marked outdated

The third row is the one that matters day to day: insert twenty lines above a comment and the thread follows its line down the file instead of pointing at whatever now occupies the old line number. Threads are matched on the text of the line, using a diff alignment, not on position.

Changes keep a stable identity across a force-push because the engine tracks a change’s identity separately from the commit that currently expresses it, so a PR whose branch was rewritten is still recognisably the same change.

Branch protection rules are keyed on a branch pattern, using the same glob dialect as workflow triggers (* stays within one / segment, ** crosses them). Exactly one rule applies to a branch — an exact-name rule wins, otherwise the longest matching pattern does. Rules do not merge, and a branch with no matching rule has no gate.

A rule can require:

  • status checks — named contexts, or CI-derived "<workflow> / <job_key>" names, all of which must be green
  • approving reviews — a count
  • up-to-date with the target branch
  • conversation resolution
  • enforcement on admins

The merge is a real git operation, not a pointer swap:

  • fast-forward when the target branch has not diverged;
  • a two-parent merge commit, produced by a three-way tree merge, when it has;
  • blocked as a conflict when both sides changed the same path.

Someone with write access can arm an open PR to merge automatically (neo pr merge <n> --auto or via the web UI). The merge is attributed to whoever armed it, not to whoever pushed the commit that turned the checks green.

When armed, the pull request enters the repository’s merge queue:

  • If the PR is up to date with the target branch, or the target branch’s protection rule does not require branches to be up to date, the PR is gated on its own head’s checks: it merges as soon as they and every other required gate pass, and no speculative candidate is built and no merge_group run starts.
  • When the target branch has moved ahead and its protection rule requires branches to be up to date, the queue builds a speculative merge candidate combining the target branch tip and the PR head, and dispatches on: merge_group runs against that candidate.
  • Once a behind PR’s merge candidate has merge_group runs under way, the queue cancels any still-running pull_request runs of those same workflows on the stale PR head and marks their checks superseded (never failed), so runners do not test both the outdated head and the merge candidate.
  • A failing check on the candidate evicts the entry from the queue and leaves the PR open.
  • Leaving the queue does not stop the candidate’s checks. If the entry is removed without its inputs moving (you dequeue it, it times out, the PR becomes a draft), the candidate and its merge_group runs are kept going. They are released and cancelled once the PR head or target branch moves, a candidate check fails, the PR closes, or 24 hours pass. Re-queuing the PR unchanged (same head, target tip, merge method, title and description, by the same person) reuses the kept candidate and its runs instead of starting new ones.
  • A new head disarms it. Pushing new commits removes the PR from the queue and requires fresh merge intent — nobody’s earlier “merge this when green” carries over to code they had not seen. An eviction belongs to the head it happened on: once the PR head moves on, the PR no longer shows the old “removed from the queue” state.
  • Stacks are linear and capped at 32; there is no branching stack.
  • Merging re-parents and retargets children but does not rebase them.
  • Cross-repository stacks do not exist — a stack lives inside one repository.