Land and its rebase conflicts
What land does with approved work. A rebase conflict is sent back to work once per run; a second fails with merge_conflict. A rebased land re-runs the checks.
Land is the workspace_land node between the human gate and ticket_close. It commits the approved handoff on the run's branch and folds that branch into main, in one node.
A conflict with main is sent back to work once per run, when the template wires conflict_outlet; a second conflict fails the run. File:line citations are in parentheses; "not checked" means the code was not read for that fact.
01what land does#
In quick-fix the wiring is human_gate (approved) → land → ticket_close → completion_proof (internal/harness/quick-fix.yaml:263-265; ticket_close is the next node after land). The same node has a conflict outlet corded back to work, in quick-fix, ticket-completion, mock-first and batch.
- Land commits exactly the sealed handoff's changed files in the run's own worktree, on the run's branch
smith/<ticket8>-<run8>. It refuses a workspace that moved since the handoff was sealed (landWorkspaceat250). The commit rules are on land-and-sync. - Then, in the same node, it calls the desk's merge hook (; the hook is
MergeLanded, wired bySetLandMergeat). A merge that fails stops the run ahead ofticket_close, unless it is a firstmerge_conflicton a node withconflict_outlet. MergeLandedwaits out a sync in flight and takes the project's sync and merge locks, so one run merges at a time. The locks are let go while the post-rebase checks run and taken again only to confirm main hasn't moved and to fast-forward (internal/desk/merge.go).
02when main has moved#
mergeRunBranch folds the branch into the clone's current branch. Nothing is ever merge-committed; main only fast-forwards.
- If the branch is already in main, nothing is done.
- Otherwise
git merge --ff-onlyis tried first; that is the case when main has not moved. - If that fails,
git rebaseof main is run in the run's worktree and, when it succeeds,--ff-onlyagain. - After a rebase, and before the fast-forward, the project's checks run (scoped, see "what is not re-run" below) on the rebased tree without the project's sync and merge locks; the locks are taken again only to confirm main hasn't moved and to fast-forward. If main moved, the land rebases and checks again, up to 3 lock-free rounds, then one round with the locks held. A fast-forward runs none. On red there is no fast-forward: the branch goes back to its land commit, the worktree is kept, and the run fails with
rebased_rednaming the failing checks, likemerge_conflict(service.ReasonRebasedRed). - A rebased land is flagged
rebasedin the landing record, and the commits are named as they are on main (applyMerged).
03when the rebase conflicts#
mergeRunBranchlists the conflicted files, runsgit rebase --abortand returns them. Main is unchanged.- The first conflict in a run whose template wires
conflict_outletis sent back to work. Land carries the run's work onto main's current head as uncommitted edits, with conflict markers in the clashing files, and emits the baton on itsconflictoutlet. The baton has nolanded, and carriescarry_conflicts,land_conflictand acontinue_notethat names the files and says to resolve them keeping both sides' intent (sendConflictBack;carryRunTreeInPlaceininternal/service/run_worktree.go). The run then goes work → checks → the gate → land again, so the operator approves the resolved work. Inbatchthe baton reachesworkwith noitem, and the work prompt says to resolve the files across the batch. - It is once per run: land counts its emissions on the
conflictoutlet in the run's journal (conflictSent), so the count survives a restart and gate decisions do not reset it.hop_limitis not involved. - The conflicted land still reports its landing, flagged
merge_conflict, andcompletion_proofignores a flagged landing: only a land that committed counts as landed (Landing.MergeConflict;requireLanded). - The thread says so in one line:
merge card: <branch> conflicts with main in <files>; sent back to work on the new main to resolve them (checks and the gate run again). No worktree is kept, because the live run still owns it, and the record is markedsent_backso the watcher does not treat the run as conflicted (;MergeLandedsets it fromservice.LandSendsBackat485). If the carry then fails, the run fails withmerge_conflictalthough the thread said it was sent back. - A second conflict in the same run fails the run with an error that starts with
merge_conflict:merge_conflict: <branch> conflicts with main in <files>; the work is on branch <branch>(service.ReasonMergeConflictat). The run's failure cause is that error. The same happens on a first conflict when the template has noconflict_outlet, or when the carry fails. - The ticket is not closed and stays open, because the run stops ahead of
ticket_close. Nothing is commented on it. - The operator is told in the run's thread and news: the branch conflicts with main in the files, main is unchanged, the worktree is kept; continue the run (a new run on the current main, then checks, the gate and land again) or resolve it by hand on the kept branch. The worktree is also listed as kept.
- A conflicted merge that was not sent back is not tried again. The thread remembers it across restarts, and the watcher leaves it until it is continued or resolved by hand.
- A rebase that fails with no conflicted file (a dirty worktree, say) is a different failure: the error is "rebasing onto failed (aborted)" and the run fails with "couldn't merge into main", not
merge_conflict.
04what is not re-run#
- The gate checks are not re-run on a fast-forward. They are before the gate:
seal_handoff,deterministic_checks,tests_route,human_gate(quick-fix.yaml:132-165), and no node after land runs checks. - A rebased land does re-run the project's checks, on the rebased tree before the fast-forward, so a clean rebase that is semantically wrong fails as
rebased_redinstead of landing. The checks of the sync on main are still a later look (see land-and-sync). - That re-run is scoped like the run's own checks, over the run's own diff from the main tip it was rebased onto (
service.ScopeLandChecks, which uses the sameplanCheckRoundas the run). Ago testcheck runs the changed packages, their importers and the guard packages (internal/harness,internal/host,internal/runtimewhen a non-test.gofile changed), or the whole./...whengo.modorgo.sumchanged orgo listfails. Thatgo testcheck also drops-race(keeping-p N) when its args are the plaintest, packages,-race,-p Nform that sharding reads, because the run's checks already raced the tree and-raceadds only runtime here; the merge line says "without -race". The run's checks node and the sync's checks keep-race. A node check runs only when a file under its dirs changed. Other project checks run as configured, and area checks do not run at all. The scope of each check is written to the merge line in the run's thread and to the landing'srecheck. The land's results are not run check results (nocheck.completedevent is journalled for them), so the sync's check-digest carry is unaffected. - Whether the same holds for every template that uses
workspace_landis not checked;ticket-completionis not re-read here.
05getting the work in after a conflict#
- The first conflict of a run is sent back to work automatically (above). Only a second conflict in the same run, or a template without
conflict_outlet, needs the fallbacks below. - A continue card, the fallback: the operator presses continue. The new run's worktree starts at main's current head, not the old run's base (
startAt := caughtUp.Base); this is pinned byTestAContinuedRunThatFailedAtLandCarriesOnlyItsOwnChangesOntoAMovedMain. The conflicted files are named to the worker incontinue_note. The old run's tree is applied onto it as uncommitted edits, three-way (carryRunTreeat). It carries the old run's own changes, including the land commit. Files that conflict keep conflict markers and are named to the worker in its continue note (; pinned byTestAContinuedRunWhoseLeftoversConflictWithMainStartsAndRecordsThePath). The continued run is a new run with its own id and starts at work (TestAContinuedRunStartsAtWorkWithTheOldRunsNewestBaton). What continue does on a real conflicting land beyond the pinned tests is not checked. - By hand, the fallback: the work is on the kept branch and worktree. Rebase it onto main yourself, run the checks, and land it again.
- Launching with
from_branchis refused when merging main into the branch still conflicts, so resolve by hand first. - Before approving, a send-back from the gate can rebase the run onto main first; it refuses and names the files if the rebase would clash.
- A real example: a KB-page run's land failed with
merge_conflictoninternal/kb/uncovered.txt; its kept branch was rebased by hand and landed again through a continue. - Two parallel runs that touch the same lines: the first approved lands by fast-forward; the second rebases onto it and, if they overlap, fails as above. Approving both gates is safe, the loser keeps its work, but it costs a continue.
- After a second conflict nothing more is automatic: the operator presses continue or resolves it by hand.