123: Factory leaks per-issue git worktrees: cleanup only removes the current run's dir (14 GB orphaned observed)#151
Merged
Conversation
added 3 commits
July 20, 2026 22:19
Member
|
Babysitter update: PR #151 is green and ready for human review at I merged the current Validation completed:
There are no open review threads or requested changes. Happy to discuss the active-lifecycle retention guard, cleanup safety trade-offs, or any other part of the implementation. Per policy, I have not merged the PR. |
added 12 commits
July 21, 2026 05:02
…force-factory-4bc95c4d
…force-factory-4bc95c4d
…force-factory-4bc95c4d
…force-factory-4bc95c4d
…force-factory-4bc95c4d
…force-factory-4bc95c4d
# Conflicts: # src/fleet/internal-fleet-client.test.ts
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.
Summary
Factory leaks per-issue git worktrees under
.factory-worktrees/<checkout>/. Completion cleanup only removes the worktree of the currently-tracked dispatch run, so any re-dispatch of the same issue under a new run identifier orphans the previous run's directory forever. On a real operator run these accumulated to 39 orphaned hoopsheet worktrees consuming ~14 GB, all for issues that were already CLOSED with MERGED PRs.Root cause
Worktree directory names embed a per-dispatch identifier that changes across runs, while cleanup is scoped to a single run:
src/orchestrator/factory.ts:10097→factoryWorktreePath,src/git/agent-worktree.ts:202) names the dir<issueSlug>-<repoSlug>-${runId.slice(0,8)}.runIdfalls back to a freshrandomUUID()(factory.ts:2575,factory.ts:2717) whenever a durable dispatch lifecycle isn't reloaded — daemon restart, takeover, or state not found for the issue.factory.ts:1814-1818) builds the worktree path with a different suffix scheme —stableHash(${pr.repo}#${pr.prNumber}:${headRef})— so the same issue can get a second worktree at a path the dispatch-path cleanup never references.#cleanupAgentWorktrees,factory.ts:4693) iterates onlyrecord.agentsof the current in-flight record and removes just that run'sspec.clonePath.git worktree prune(called insideGitAgentWorktreeManager.cleanup,src/git/agent-worktree.ts:116,123) only drops admin records for directories that are already gone — it never deletes a live directory.Net effect: there is no per-issue sweep that removes
.factory-worktrees/<checkout>/<issueSlug>-<repoSlug>-*across all stale run identifiers. Every re-dispatch under a newrunId(or the babysitter's hash scheme) strands the prior directory.terminalState: human-review+mergePolicy: neveramplify this: issues linger in-flight longer and are re-dispatched / babysat / survived across daemon restarts more often, each restart minting a newrandomUUIDrunId.Evidence (observed)
.factory-worktrees/hoopsheet/, ~14 GB, 0 running hoopsheet agents.Proposed changes
A. Per-issue worktree sweep on completion
When an issue reaches its terminal state (
#finishDurableRelease→#cleanupAgentWorktrees,factory.ts:3082/4693), remove all worktrees matching that issue, not just the tracked run:.factory-worktrees/<sanitize(basename(baseClonePath))>/<issueSlug>-<repoSlug>-*for each routed repo.GitAgentWorktreeManager.cleanupteardown (git worktree remove --force+git worktree prune+removeEmptyWorktreeParents), reusingassertSafeWorktreeso nothing outside.factory-worktrees/<checkout>/is ever touched.B. Startup reaper for issues no longer in flight
On daemon start (after in-flight registry load), reap orphans whose issue is not in the current in-flight set:
.factory-worktrees/<checkout>/*directories.<issueSlug>is not an active in-flight issue for this workspace, and which is clean (no uncommitted changes, no unpushed commits, no lock), tear it down via the same safe path.log()a one-line summary of how many were reaped and how much was reclaimed. Never silently skip dirty worktrees — surface them so an operator can decide.Acceptance criteria
<issue>-<repo>-*worktree for that issue, not just the last run's..factory-worktrees/<sanitize(basename(baseClonePath))>/(reuseassertSafeWorktree); a path outside that root throws rather than deletes.agentWorktreesCleaned/agentWorktreeCleanupFailures(factory.ts:4704,4707) and add a reaped-on-startup counter.src/orchestrator/factory.test.tsand/orsrc/git/agent-worktree.test.ts:.factory-worktrees/<checkout>/is refused.Out of scope (follow-ups)
runIdvs babysitterstableHash(pr#head)) so an issue always resolves to a single stable worktree path. Worth doing, but the sweep/reaper above fixes the leak regardless..factory-worktreeslocation or a retention window configurable.Notes for the implementer
GitAgentWorktreeManager.cleanupandassertSafeWorktree— do not hand-rollrm -rf.npm test(vitest); add the tests above.runIdis minted, let it complete, and confirm the first run's.factory-worktrees/<checkout>/<issue>-<repo>-<oldhash>directory is gone.Fixes #123
Summary by cubic
Stops leaking per-issue git worktrees by sweeping all runs on completion and reaping clean orphans on startup. Adds symlink-proof root/container checks, strict realpath/registration validation, pre-cleanup inspection (git locks, uncommitted/unpushed), and reclaimed-size reporting; addresses Linear #123.
New Features
dispatch-owner) excludes active issues from durable lifecycles, waiting clarifications, and online legacy agents; reaps orphans (including stale legacy registries) and logs reclaimed size.listWorktrees+factoryWorktreeIssueSlugfind all<issue>-<repo>-*runs across configured checkouts for completion cleanup and startup reaping.inspectForCleanupreturnsbytesandretentionReasons; new portsAgentWorktreeRepositoryandAgentWorktreeCleanupInspection.Bug Fixes
/.factory-worktrees/<checkout>/.retentionReasonsexist; counters updated (agentWorktreesCleaned,agentWorktreeCleanupRetained,agentWorktreesReapedOnStartup,agentWorktreeCleanupFailures).Written for commit 994abbf. Summary will update on new commits.