skip to content
Jobs

Safe Finalize in a Shared Checkout

Undo only your own changes, lock master while you mutate it, and re-gate the merge so a concurrent run never loses committed work.

Gantry milestones
Safe finalization when the checkout is shared between runs.

A Gantry run does its work on a throwaway copy of the repository and finishes by merging that work back into the project's main branch — its `master` checkout. That finishing step was written as though the run owned the checkout by itself. On a busy machine that is false: several runs finish against the same `master` at once, and people commit to it by hand at the same time. The old code coped by remembering where `master` pointed when it started and hard-resetting back to that spot whenever anything went wrong. That quietly discarded commits other runs and people had landed since, and a blanket clean deleted their untracked files along with them. The same path staged everything in the checkout when it recorded its own bookkeeping, folding a stranger's half-finished edits into a Gantry commit, and nothing ever re-checked that the merged `master` still built.

This job made finishing safe when the checkout is shared. A give-up now undoes only the run's own uncommitted change to `master` and can never rewind committed history; a lock keyed on the shared checkout means two runs cannot mutate `master` at the same moment; bookkeeping commits name the exact files they own instead of sweeping the tree; and the test suite runs again on the merged result, so a merge that broke the build without any textual conflict fails loudly rather than passing as success.

Build

The change arrived as one milestone over a single tightly coupled area — rollback, the merge core, and the confinement check — and its interest is the order those coupled pieces were cut, so the independent fixes and the lock could each be proven before the risky core leaned on them.

The milestone brief is explicit that these fronts share one code surface and cannot be reworked in isolation, which makes the sequencing the real design decision. Two fronts touch neither rollback nor the merge core: scoping the confinement check so a sibling run's `.gantry/` bookkeeping stops reading as an escape, and committing archived logs by explicit pathspec instead of staging the whole checkout. Both were landed first because each is self-contained and testable on its own, which de-risks the coupled core that follows — a cut that lets the cheap wins be proven against the real git plumbing before the dangerous rework begins.

The lock is the clearest case of proving before leaning. Rather than wire a `flock`-based checkout lock straight into finalize, the plan built it as a standalone primitive with its own cross-process tests and the engine's behaviour left byte-for-byte unchanged, then wired that proven primitive across the read-tip-to-merge critical section in a separate sprint. The non-destructive rollback — combining the "undo nothing that isn't ours" rework with merging onto the live tip, because both edit the same give-up paths — landed before the serialization that depends on it, and the post-merge re-gate came last precisely because it has to run inside the already-held lock.

Where the cut came under stress is legible in the run. The review after the pathspec sprint and the review after the rollback-core sprint each forced a re-plan, redrawing the remaining work rather than pushing on. The run's single execute error then landed on the final front, the post-merge re-gate; the recovery re-ran the lock-wiring sprint before the re-gate finally landed. That locates the difficulty exactly where the briefs said the coupling was tightest — the point where the held lock and the re-gate have to compose — and the earlier standalone proving of the lock is what kept that last difficulty contained to those two sprints instead of the whole finalize path.

Feature

Gantry's finish once ended at a hard reset to a remembered tip, a recursive clean, and an unverified merge; this job replaced that with a non-destructive rollback, a per-checkout mutation lock, pathspec-scoped commits, and a gate re-run on the merged master.

Before this job, the guarantee that a run never damaged a shared `master` rested on rewinding it after the fact — a hard reset to a captured position and a `git clean` of the whole tree — which is the very mechanism that destroyed concurrent work. There was no serialization between two finishing runs, the archive commit staged the whole checkout, and a merge that produced a broken-but-textually-clean `master` reported success. The design that answers this keeps `master` correct by never writing to it speculatively: undo only the run's own uncommitted merge, hold one lock across the whole finalize sequence, stage by explicit path, and re-run the gate on the merged tree before declaring success.

Standing in the live tree, the shape holds. `src/engine/checkout_lock.rs` exists as its own module, still carrying the long doc comment that explains the per-checkout key, the `flock` advisory lock, and RAII release; most of what the job wrote there is still present. `finish.rs` acquires that lock through a shared `acquire_checkout_lock` helper and holds it across every `master`-writing path, and `gate.rs` carries `regate_main`, the post-merge gate that re-runs the suite on the merged checkout under the same held lock. The confinement-scoping and rollback changes in `worktree.rs` remain largely intact.

The parts that have moved are worth naming plainly. The finalize path itself and its tests have been substantially reworked since the job landed — barely half of the `finish.rs` lines and only a fragment of the finish tests survive verbatim — which is ordinary churn on a hot path, not a reversal: the capability is still present and documented. The small change the job made to the map driver is gone from `HEAD`; a map run has no whole-plan gate to re-run, and that carve-out now lives inside `regate_main` rather than in the driver. Every file the job touched still exists at `HEAD`, and the four capabilities it added are all still the ones the finalize path uses.

unavailable sessions
unavailable context
102,286 in / 289,864 out tokens
$49.46 cost
0 x 6 milestones x sprints
unavailable edits
unavailable commands
1h 38m duration
8 execute · 7 review · 2 plan · 2 replan · 1 gate-build roles
0 x 2 fixes x replans
- harness