skip to content
Jobs

Resolving Merge Conflicts Off the Main Checkout

Move conflict resolution into the throwaway worktree so a failed merge never dirties your real tree.

Gantry milestones
Move conflict resolution into the throwaway worktree so a failed merge never dirties your real tree.

When a Gantry run finishes, it merges its work back into your main branch. If that merge hit a conflict, the old behavior fixed it right there in your real checkout: it ran a resolver agent with the main repository as its working directory, told it to merge and resolve in place, and — when the agent could not finish — stopped and left the conflict markers and a half-merged index sitting in the tree you actually work in. This job moved that whole operation off the main checkout. The conflict is now integrated and resolved inside the disposable worktree Gantry already builds every run in; the tests are re-run there; and the main branch is touched only by a merge that has been proven cannot conflict. When resolution fails, main is returned to exactly the state it was in before the merge began, and the run stops with a message naming the worktree and branch where the unfinished work is left for a person to pick up.

Build

The work split into two sprints: the low-level git operations that probe and stage a merge without dirtying main, then the control-flow rewrite that composes them into the run-finish path. Separating them let the risky git work be tested against real throwaway repositories before any behavior change relied on it.

The boundary that made the work agent-sized falls between the git plumbing and the behavior change that consumes it. The first sprint delivered a set of operations on the worktree type — detect whether merging the branch into main would conflict without leaving main dirty, integrate main's tip into the branch from inside the worktree, verify the branch is now trivially integrable, and snapshot then restore main's exact pre-merge state. Every one of these could be written and tested against real temporary git repositories with no resolver, no agent, and no finish path yet in existence: a detection probe that asserts main's status is empty afterward, a capture-and-restore round trip run against a deliberately dirtied checkout. That is what the split bought — the part most likely to go wrong, the git operations that must never touch the user's tree, became a testable unit before anything rode on it.

The second sprint drew on those operations, replacing "resolve in the main checkout" with "integrate, resolve, and re-run the gate in the worktree, then merge into main only once verified." Its acceptance gate constructs a genuine conflict between the run branch and an advanced main and requires main to stay byte-for-byte clean on both the success and the give-up paths — a check the plumbing-first ordering made possible to write plainly, because the pieces it exercises were already grounded.

The difficulty was dissolved ahead of time rather than surfaced under load. The run stayed green from start to end: no sprint was retried, and no review forced the remaining work to be re-planned. The only extra motion was a review pass on the control-flow sprint that re-ran the gate before landing — the pipeline re-grounding a safe adjustment, not a boundary giving way.

Feature

Gantry's run-finish used to resolve merge conflicts in the user's real checkout and could leave it dirty on failure. This job added the worktree-side git operations and rewired the finish path so a conflict is integrated, resolved, and re-tested in the disposable worktree, main altered only by a proven merge.

Before this job, the end of a run trusted the main checkout to be where a conflict got fixed. The resolver agent ran there and resolved in place; a resolution that failed dropped the run into a stop path with no rollback, leaving an unmerged index and stray conflict markers in the tree the user works in daily.

The answer was a layer of git operations on the worktree type plus a rewritten finish path. Gantry now integrates the current main tip into the run branch inside the disposable worktree, runs the resolver agent there, re-runs the real test gate against the integrated tree, and confirms the branch is trivially integrable — and only then merges into main. A failure returns main to its captured pre-merge state and stops with a message naming the worktree and branch, so the unfinished work survives for inspection and the real tree stays clean.

At today's HEAD the git operations are still here and still tested: the worktree module carries the conflict-detection probe, the in-worktree integration, the integrable check, and the capture-and-restore snapshot, alongside unit tests that build real conflicts and assert main is never dirtied. The control-flow half has moved — the single build source file this job edited no longer exists; the finish and gate logic now lives in a build module split into its own directory. And the design grew rather than sat still: the live finish path integrates main into the worktree on every merge, not only when there are textual conflicts, and gates that integrated tree, so a tree that merges cleanly but fails its tests is treated as resolution work as well. That later behavior folded onto the foundation this job laid rather than replacing it.

unavailable sessions
unavailable context
38,993 in / 95,071 out tokens
$10.93 cost
0 x 2 milestones x sprints
unavailable edits
unavailable commands
25m duration
unavailable roles
0 x 0 fixes x replans
- harness