fix(worktree): never auto-discard a named agent's unintegrated work on PTY exit - #496
Open
snehithareddy28 wants to merge 1 commit into
Open
snehithareddy28 wants to merge 1 commit into
snehithareddy28 wants to merge 1 commit into
Conversation
…n PTY exit The moment a named agent's terminal exited, teardownPty force-removed its isolated worktree — `git worktree remove --force` — with no check for work that had never been integrated. The keep-if-unintegrated protection existed only for ephemeral workers, so the same code path that preserves a worker's half-finished branch destroyed a named agent's. One rule now covers every isolated agent: releaseWorktree() (git.ts) removes a worktree only when it is clean AND fully contained in the branch it was cut from; otherwise it and its branch stay, and the caller is told why. It fails safe — whatever git cannot answer counts as work. Named agents go through it on every teardown route (natural exit, crash, kill from the UI or by voice). A preserved worktree is logged, reported to god with the exact command to remove it by hand, and tracked in the same list the GC sweep already drains once the work lands in base — without touching the agent's HIVE_ROOT/agents/<id> dir, which is its memory, not scratch. The base branch is now recorded at spawn (worktreeBases) instead of being computed and thrown away; a worktree tracked without one is compared against the parent repo's current branch, and kept when even that is unknown. The GC sweep also skips an id that is live again as a named agent (restore re-enters the worktree by cwd), not just a live worker. Five tests in test/worktree-release.test.cjs on real repos: clean and integrated → removed; uncommitted changes → preserved; commits the base lacks → preserved (branch survives); released once merged; git cannot answer → kept. Closes chaitanyagiri#297
This was referenced Sep 12, 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.
What & why
Closes #297. When a named agent's terminal exits,
teardownPtyforce-removes its isolated worktree —git worktree remove --force— with no check for work that was never integrated. The keep-if-unintegrated gate only existed for ephemeral workers, so the same teardown that preserves a worker's half-finished branch destroys a named agent's. As the issue says, this is the dangerous one of the restore family: the others break a restore, this one destroys work. The fix is the one the reporter asked for — apply the protection that already exists to the case it skipped.One rule for every isolated agent.
releaseWorktree()ingit.tsremoves a worktree only when it is clean and fully contained in the branch it was cut from; otherwise the worktree and its branch stay, and the caller is told why. It fails safe: whatever git cannot answer counts as work. Named agents now go through it on every teardown route — natural PTY exit, crash, kill from the UI, kill by voice. A preserved worktree is logged tolog.jsonl, reported to god with the exactgit worktree removecommand to drop it by hand, and tracked in the same list the GC sweep already drains once the work lands in base. The agent'sHIVE_ROOT/agents/<id>directory is its memory, not scratch, so the sweep never reclaims it alongside the worktree.Two small enablers. The base branch is now recorded at spawn (
worktreeBases) instead of computed and thrown away; a worktree tracked without one is compared against the parent repo's current branch, and kept when even that is unknown. And the GC sweep skips an id that is live again as a named agent (restore re-enters the worktree by cwd), not only a live worker.Type of change
Evidence
Before
After
Notes for review:
test/worktree-release.test.cjs, each on a real repo with a realagent/<id>worktree: clean and integrated → removed (and gone fromgit worktree list); uncommitted changes → preserved with the file still there; commits the base lacks → preserved, branch intact; the same worktree released once god merges the branch; an unanswerable base → kept.finalizeWorkerWorktreeis untouched; workers keep their scratch-dir handling.finalizeAgentWorktreeis its named-agent sibling withscratchDir: null, and the tworemoveWorkerScratchcalls in the sweep are gated on that field.npm run typecheckand the fullnpm run test:focusedsuite (839/839 on this branch) pass locally.Discord: asr2805