fix(pool): preserve a reclaimed worktree that holds unlanded work - #80
Open
leecalcote wants to merge 1 commit into
Open
fix(pool): preserve a reclaimed worktree that holds unlanded work#80leecalcote wants to merge 1 commit into
leecalcote wants to merge 1 commit into
Conversation
acquire keyed a slot's reusability on live-owner presence and a dirty check, but not on whether the slot was ahead of its base. A worktree holding committed but unlanded work has a clean working tree, so IsDirty returned false and the slot passed the check; when its owner_pid no longer referred to a live process (a crashed agent, or a reboot leaving stale PIDs in the state file), acquire reset it via checkout --detach --force + reset --hard + clean -fd, destroying the unlanded commits. Add hasUnlandedWork: a slot holds unlanded work if it is dirty or its HEAD is not merged into the default branch. It fails closed - an indeterminate state is treated as holding work - so the reuse reset only ever runs on a clean, fully merged worktree. A slot holding unlanded work is preserved; acquire uses another slot or creates a new one. Adds red-green regression coverage: an ahead-of-base clean slot with a dead owner is preserved, and a clean merged slot with a dead owner is still reused. Signed-off-by: Lee Calcote <7570704+leecalcote@users.noreply.github.com>
This was referenced Aug 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Root cause
internal/pool/pool.go'sacquirereuse loop keys a slot's reusability on live-owner presence and a dirty check, but not on whether the slot is ahead of its base. A worktree holding committed but unlanded work (a feature branch, or a detached HEAD ahead of the default branch) has a clean working tree, sogit.IsDirtyreturns false and the slot passes the check. When itsowner_pidno longer refers to a live process - a crashed agent, or a host reboot that leaves the state file with stale PIDs -ownerAlivereturns false and the slot reads as available.acquirethen callsResetWorktree(checkout --detach --force+reset --hard+clean -fd), destroying the unlanded commits. Owner-process death does not make committed-but-unlanded work safe to discard.Fix
hasUnlandedWork(repoRoot, wtPath): a slot holds unlanded work if it is dirty or its HEAD is not merged into the default branch (git.IsHeadMergedIntoDefault). It fails closed - if either check cannot be evaluated, the slot is treated as holding work, so a reclaim never destructively resets a slot whose state cannot be proven clean.acquire, replace the dirty-only skip withhasUnlandedWork, so the reuse reset only ever runs on a clean, fully merged worktree. A slot holding unlanded work is preserved;acquireuses another slot or creates a new one.The default-branch merge ref comes from the existing
git.IsHeadMergedIntoDefault/DefaultBranchMergeRef, which already fails closed against a stale remote tracking ref, so an ahead-of-base slot is never misclassified as reusable.acquirefetches origin before the reuse scan, so the tracking ref is fresh.Verification
TestAcquire_PreservesUnlandedWorkWhenOwnerIsDead(new): a clean worktree holding a committed, unlanded change, with a dead owner PID, is not reused -Acquirehands out a different slot and the unlanded commit and its file survive untouched. Red-green: without the fix this test fails with "Acquire reused the slot holding unlanded work".TestAcquire_ReusesCleanMergedWorktreeWhenOwnerIsDead(new): the guard does not over-refuse - a clean, fully merged worktree whose owner is gone is still reused.gofmt -l .clean;go vet ./...passes;go test ./...passes.Fixes the data-loss defect reported in #79.