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.
0 milestones 6 sprints
4 plan 8 execute 7 review
1h 45m total 13m 20s per sprint
50.8M tokens in 289.9k tokens out
$49.46 nominal cost
A Gantry run finishes by bringing its branch back to the project's main checkout. This job addressed the case where that checkout is shared by concurrent runs and human work, so finalize could not rely on exclusive ownership of `master`.
The work changed the finish path from cleanup-after-the-fact to prevention and verification. Shared bookkeeping stopped reading as an escape, archive commits named their own paths, rollback stopped rewinding committed history, checkout mutation became serialized, and the merged result now has to pass the gate before the run can report a clean finish.
How this walkthrough is structured
Feature
What did the run build and ship?
The tree now carries a shared-checkout finalize path that ignores sibling run state, commits only owned archive paths, aborts only its own unfinished merge, serializes branch mutation, and checks the merged branch before success.
Build
How did Gantry structure the work?
The cut separated checkout-safety work into independently provable hazards first, then the rollback core, then lock wiring and post-merge verification where the run record shows the coupling tightened.
Confinement Check Scope
2 agents 8m 29s wall time 19.8k tokens out $3.43 nominal cost
The confinement sweep still filters run-state bookkeeping at the porcelain-diff layer in `worktree.rs`. Both the pre-run baseline and the later sweep inherit that filter because the exclusion lives in `newly_dirty_porcelain`, and tests cover sibling run state alongside a genuine escape. The feature is still visible as a shared checkout backstop rather than as a finalize-only special case.
This opening sprint carried the one front outside the finalize and merge core. Its brief narrowed the confinement diff so a fresh agent could work on the status comparison itself, not the later rollback machinery. That made the boundary small: prove that sibling `.gantry` run state is ignored while real project-file escapes still fail. The run facts show this unit stayed green, which fits a self-contained primitive-level change.
Pathspec Shared Checkout
3 agents 11m 8s wall time 24.4k tokens out $4.23 nominal cost 1 replan
Resolver-log archiving now copies the logs into the main checkout and commits only those archive paths. `archive_resolver_log` builds the owned path list, and `commit_main_paths` stages and commits those paths rather than the whole checkout. The regression test still drives real git with foreign staged and untracked files present, so the behavior remains tied to the original shared-checkout failure mode.
This unit handled the other early independent hazard: commits made in the shared checkout had to stop staging unrelated work. The brief pointed at the resolver-log archive path and asked for a pathspec discipline already used by cleanup commits, keeping the scope narrow enough for one agent to audit the main-writing commit helpers. Review then forced a re-plan, so this boundary produced new information for the remaining finalize work without disturbing the completed fix.
Checkout Mutation Lock
2 agents 5m 29s wall time 18.7k tokens out $2.57 nominal cost
`src/engine/checkout_lock.rs` remains a standalone module with the per-checkout lock and its process-level tests. The module resolves the shared git common directory, uses a stable lock file there, exposes non-blocking and interruptible blocking acquisition, and releases through the guard lifetime. Later edits expanded its comments and usage, but the primitive from this unit is still the one finalize uses.
The lock was split out before any finalize wiring, which gave a fresh agent a clean contract: key a crash-safe advisory lock on the checkout and prove its contention behavior in isolation. That was a useful cut because the later critical section work could depend on a tested primitive instead of mixing lock design with rollback edits. The run record shows extra gate activity here, but no red outcome or retry, so the additional check stayed inside this unit's proof boundary.
Safe Rollback And Merge
3 agents 15m 54s wall time 55.6k tokens out $9.44 nominal cost 1 replan
Rollback in the live tree no longer treats the captured main tip as a reset target. `MainSnapshot` is documented as read-only, and `abort_main_merge` defensively clears an in-progress merge without hard-resetting the shared checkout or cleaning untracked files. The current tests still assert that concurrent committed work and untracked files survive a give-up, while merge probing remains non-destructive and reads the branch state at merge time.
This was the central core rewrite, combining non-destructive rollback with live-tip merge behavior because both touched the same give-up paths. A fresh agent had to reason across conflict resolution, snapshots, merge probes, and tests that previously encoded the bad reset contract. Review triggered another re-plan after it landed, which is the run's clearest sign that this sprint changed the shape of what remained. The boundary was heavy, but it left serialization and post-merge checking for later units rather than carrying every finalize concern at once.
Serialize Master Mutation
4 agents 30m 39s wall time 89.7k tokens out $17.54 nominal cost
`finish.rs` now acquires the checkout lock before the code reads and mutates the home branch, and the same helper is used by the partial merge paths. The wait emits a checkout-lock event and remains interruptible, while comments document the lock ordering against the gate lock. The concurrency regression still exercises two real finalizers against one checkout and checks that the later merge descends from the earlier landed commit.
This sprint took the already-proven lock and moved it across the real finalize critical sections. Its brief was a wiring task with a strict boundary: full finalize, partial finalize, and offline merge all had to share one checkout-level exclusion, while the rollback semantics from the prior sprint handled failure cases. The run record shows this unit was started again after the final sprint's execute error, so the lock wiring became part of the recovery surface when post-merge verification had to compose with it.
Post-Merge Master Re-gate
3 agents 15m 32s wall time 52.3k tokens out $7.51 nominal cost
`regate_main` still runs the configured gate with the main checkout as its working directory after merge-back. A green result lets finalize complete, a red result returns a distinct post-merge verdict and leaves the merge in place for a forward fix, and map runs without a whole-plan gate are explicitly skipped. The feature survives as a named post-merge gate path rather than an implicit assumption inside merge success.
The final unit depended on the lock being held across finalize, because the merged branch had to be checked before another run could build on it. Its first execute attempt errored, and the run recovered by revisiting the lock-wiring unit before this sprint passed, locating the hard boundary at the lock and gate composition. Once that integration held, the rest of the unit was a clear verification rule: a textually clean but broken merge must stop loudly.