fix: reclaim stranded worktrees and stop repeating issue status comments - #180
Merged
Merged
Conversation
Two faults combined to strand a repo permanently and then announce it dozens of times on the issue. Worktree cleanup only ran in a `finally` inside each run, which covers the success and throw paths but not the process dying. A crash, a kill, or a machine restart mid-run left the worktree registered forever, and nothing swept it — `worktreeRootDir` was referenced only where paths are built. Once enough leaked, `assertRepoCacheCanBeRecloned` refused to heal the cache and that repo's automation stopped for good. `git worktree prune` could not help, because it only clears registrations whose directory is already gone. Orphans are now reclaimed: on startup, where the single-instance lock proves none can legitimately be in use; on the watcher tick, for any worktree this process is not actively holding; and before refusing a reclone, so an already stuck cache heals itself. Worktrees belonging to live runs are tracked and never touched, and an unresolvable registration still refuses rather than guessing. Issue status comments were posted with `createComment` every time, with no marker and no dedupe. Each recovery cycle added a fresh "started" and "failed" notice, so a permanently failing issue accumulated identical comments — the PR side already solved this with an anchored reply it edits in place. Issue comments now carry the same kind of hidden marker and update the existing comment, with an attempt count so a reader can still tell it retried. The runtime's worktree sweep is injectable and stubbed in the runtime tests, so the suite never touches the real PatchDeck home. Verified: npm run check, npm run test:all (809 tests), npx eslint ., npm run build.
Contributor
📄 Documentation Review ReminderThis PR changes source files but does not update any public documentation. Changed source files: Please check if documentation in
Available docs: getting-started.md, pr-babysitter.md, agent-dispatch.md, configuration.md, pr-questions.md |
Merged
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
Reported from the field:
❌ Issue work failed — Reason: Refusing to reclone repo cache while 22 registered worktree(s) still exist, repeated dozens of times on the same issues. That is two separate bugs compounding.Worktrees were never reclaimed after a crash
removePrWorktreeis called fromfinallyblocks inbabysitter.ts,issueWorkAgent.ts, andciHealingAgent.ts, which covers the success and throw paths — but not the process dying. A crash, kill, or machine restart mid-run stranded the worktree, and nothing swept it:worktreeRootDirappeared only inpaths.ts(definition) andrepoWorkspace.ts(path construction).They only accumulated. Once the cache needed a reclone,
assertRepoCacheCanBeReclonedrefused and that repo's automation was dead until a human intervened.git worktree prunecould not save it — prune only clears registrations whose directory is already gone, and these directories still existed, so the count never dropped.This is the exact failure class #174 set out to eliminate, and the retry policy made it visible: the error classifies as
retryable, so it retried, parked, and the recovery sweep revived it hourly forever.Orphans are now reclaimed in three places:
includeActive, becauseinstanceLockguarantees no worktree can legitimately be in use.activeWorktreePathsand skipped.A registration whose
gitdirpointer cannot be resolved still refuses rather than guessing, and the existing safety tests for that and for active workspaces still pass unchanged.Issue status comments repeated instead of updating
postIssueWorkStatusCommentcalledcreateCommentunconditionally — no marker, no dedupe. Every recovery cycle added a fresh "started" and a fresh "failed" notice, so a permanently failing issue collected identical comments indefinitely.The PR path already solved this with an anchored reply it edits in place (
postStatusReplyForFeedbackItem/updateStatusReply); issues never got the same treatment. They now carry a hidden<!-- patchdeck:issue-work-status:owner/repo#N -->marker and update the existing comment. Because in-place editing would otherwise hide that anything retried, the comment carries an attempt count.Test isolation
The new sweep does real git and filesystem work, which made the watcher tests flaky by touching the real PatchDeck home. It is injectable as
reclaimOrphanedWorktreesFnand stubbed across the runtime tests.Verification
npm run check— cleannpm run test:all— 809 tests, 0 failures (ran three times to confirm the flakiness is gone)npx eslint .— cleannpm run build— succeedsNew coverage: reclaiming a stranded worktree so a blocked reclone proceeds; the sweep reporting what it cleared and being idempotent; an in-flight worktree surviving the sweep; and three consecutive failing issue-work runs producing exactly one comment instead of three.
Related Issue