feat(dev): lab — unified teardown + worktree/branch cleanup #140
No reviewers
Labels
No labels
bug
enhancement
in-progress
needs-info
needs-triage
p0
ready-for-agent
ready-for-human
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/nixos!140
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/135"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Slice 2/3 of the per-instance-worktrees design (ADR-0017). Collapses every teardown onto one guarded rule and adds the cleanup sweeps so parked worktrees/branches don't leak.
Stacked on #139 — read first
This is blocked by #134 and built on its branch: the PR targets
afk/134(slice 1, PR #139), so the diff shows only slice 2. Merge #139 first, then either merge this intoafk/134or retarget it tomain. Basing onmainwould have mixed slice 1's ~1k-line refactor into this diff and risked double-applying it.What changed
teardownInstance→teardownGuarded(dirty → keep worktree+branch; clean → remove worktree, delete branch iff merged), replacing slice 1's remove-on-success / keep-on-failure. Manual Stop, startup reconciliation, and the runtime sweep use the same rule. AFK keeps only its outcome accounting (consecutive-failure counter + budget-clock neutrality); a manual AFK Stop stays neutral (keeps worktree +afk/<N>branch, ADR-0013).reconcile.go): every orphan worktree (alab//afk/worktree with no live session) gets the guarded teardown; every merged danglinglab//afk/branch is deleted — finally GC'ing theafk/<N>claim branches lab kept forever. Runs synchronously before the schedulers, so no Start races it.afkSweepInterval(10 min, not every 30s tick); best-effortgit fetchper project, then merged-only GC oflab//afk/branches + their clean worktrees. Never touches dirty/unmerged.Fetch,Worktrees(porcelain parse),Branches.startingset before its worktree exists and cleared once its session is live, so the sweep can't remove a not-yet-live worktree out from under an in-flight Start (a fresh branch reads as "merged"). Without it the sweep would introduce a data-loss race (caught in adversarial review).Acceptance criteria
TestReconcileWorktrees_realGitCrashRecovery(real git).lab/andafk/branches + their clean worktrees are deleted at runtime; dirty/unmerged are never auto-removed —TestSweepProject.go test ./...passes.Verification
gofmt,go vet,go build,go test -race ./...all pass locally (lab Go is not built by the eval-only pre-commit). The pre-commitfweval passed on commit.Note (pre-existing, self-healing)
A failed
git worktree add -bcan leave a branch at the base commit with no worktree; the Start rollback doesn't delete it. Since such a branch sits atorigin/<default>it reads as merged, so the new sweep GCs it within a sweep interval. Pre-existing (slice 1), out of scope here, and now self-heals via this PR's sweep — flagged for transparency.Closes #135
Validation: PASS — landing now (retargeted to
mainafter #139).afk/134; with #139 merged, I retargeted this PR tomain, so its diff is exactly slice 2 (10 files) andCloses #135fires on merge to main.afk/135(cumulative slice1+slice2):gofmt/vet/buildclean,go test -race ./...green (43.2s), incl.TestReconcileWorktrees_realGitCrashRecoveryandTestSweepProject(real git).reconcile.gorace guard checks out —gatherRefsreads branches → worktrees →starting→live, withowned = starting ∪ live, closing the window where an in-flight Start's fresh (merged-looking) branch could be GC'd.managedBranchonly toucheslab//afk/branches; every teardown routes through the oneteardownGuarded.secrets.yaml/stateVersion;Closes #135present.Merging via merge-commit.