Repository navigation
fix(server): cleanup follows the branch an agent checked out - #68
Merged
Merged
Conversation
Maintenance kept every worktree whose agent had switched branches, because nothing wrote HEAD back to the thread. A new WorktreeBranches service reads HEAD and rebinds the threads on that worktree. Cleanup keeps for one pass after a rebind, then applies the existing rules. A detached HEAD stays kept unless a ref outside refs/t3 holds its commit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A thread kept its old branch name after its agent switched branches, so the pull request refresh skipped it and cleanup had to repair it later. Run finalization now syncs the worktree branch before that refresh. A sync failure only logs, so it cannot block the next turn. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Launch carried its own copy of the loop that writes a branch to every thread on a worktree. It now calls WorktreeBranches.sync after the rename, so one path writes thread.branch for a worktree. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Pins the resume half of the cleanup change. Once a thread is rebound and its worktree is removed, the next turn recreates the checkout on the branch the agent switched to, at that commit, and not on the stale name. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A deleted owner's record cannot be rewritten, so only its surviving subagents are rebound. This pins that the group comparison still lets removal proceed on the next pass, and that the branch ref survives. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
…lone An independent check of the cleanup change found three ways to lose or strand work. A different repository at the worktree path had its branch written to the thread, so the thread forgot its own branch. A branch another checkout holds was recorded even though the thread could not recreate its worktree there. And the detached HEAD check ran before later steps that gave a holding ref time to go. Sync now compares the worktree's repository with the project's before it writes, and treats a branch another checkout holds like the default branch. Cleanup checks the holding ref last, immediately before removal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cleanup counted any remote-tracking ref as holding a detached commit. A routine `git fetch --prune` removes a remote branch that was deleted upstream, so the commit could become unreachable after its worktree was gone. Only local branches, tags, and the remote's default branch count now. A detached worktree held by any other remote branch stays kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y thread Adversarial review reproduced more ways for this change to lose work or never finish. Removing a detached or default-branch worktree could lose a commit only its reflog held, or strand a thread whose recorded branch another checkout held. A remote-tracking ref counted as protecting a commit even though a routine prune or a stale origin/HEAD could remove that protection. A run end skipped a sibling whose write had been deferred to it. Threads with a trailing slash in their path or no recorded branch never rebound, and mismatched records on a detached or default-branch worktree were kept forever. A held index lock blocked rebinding, and main counted as the default branch when the remote's default was trunk. Cleanup now checks HEAD and the reflog against local branches and tags only, and keeps a worktree while another checkout holds a recorded branch. Sync compares HEAD with every thread on the worktree instead of the caller's record, reads HEAD without depending on the index lock, finds threads by the path each one stored, and asks the remote when no local HEAD ref names its default. Model: Opus 5.5 Harness: Claude Code in T3 Code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An adversarial review of this branch reproduced commit loss that main prevents. A rebound worktree counted as bound on its next pass, which skipped the reflog check, so cleanup removed it and gc then pruned commits only its reflog held. A deleted owner with no surviving threads skipped the same check. Another repository's linked worktree at the path skipped the repository check and lost its untracked files to `git clean`. Two detached worktrees recording one branch were both removed, and with no local origin/HEAD a default other than main or master could become a thread's recorded branch. The reflog check now runs on every worktree. Rewritten commits land in the reflog too, so an amended or rebased worktree waits until gc expires those entries. Cleanup compares the worktree's repository with the project's before it reads the checkout, by file identity so a differently cased path still matches. It keeps a detached or default-branch worktree while a thread elsewhere records the same branch. The service asks the remote for its default whatever the name, once per checkout. Model: Opus 5.5 Harness: Claude Code in T3 Code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ScriptedAlchemy
enabled auto-merge (squash)
November 6, 2026 00:08
The upstream sync split Ultracode out of the effort list into a boolean option. pstack pins every option except speed, so a role at effort=xhigh now resolves with ultracode false, which is what that effort meant before: not Ultracode. This test is fork-only, so the sync did not update it, and it left main's required Fork Check failing for every pull request. Model: Opus 5.5 Harness: Claude Code in T3 Code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Why
Maintenance kept every worktree whose agent had switched branches. T3 records a thread's branch at launch and never reads the checkout again, so one
git checkoutby an agent left the record stale. Cleanup then kept the worktree before it checked age, settlement, or merge state. A replay of the keep rules over one maintainer's instance found this check holding 75 of 107 worktrees. Nearly all of those belonged to delegated child threads, and 73 of the 87 threads on them had already settled.What changed
WorktreeBranchesservice reads HEAD and rebinds every thread on a worktree to the branch that is checked out. Run finalization, thread launch, and cleanup all call it, and the launch rename loop is deleted.git fetch --pruneremoves them and a staleorigin/HEADcan misname the default. Rewritten commits land in the reflog too, so an amended or rebased worktree waits untilgit gcexpires those entries, 30 days by default.origin/HEAD, the service asks the remote for its default whatever the name, once per checkout.Scope
Tradeoffs
Blast radius
The change is server only. Clients already follow the existing metadata event, so branch labels need no client change. Every worktree thread now runs one small git read and one thread lookup at the end of each turn. The lookup took about 5 ms on a real database with 1,135 threads. A thread left on the default branch runs a few more local reads each turn, plus one
ls-remoteper server lifetime when noorigin/HEADexists. The old check also prevented unreachable commits and resume on a stale name. This change makes both protections explicit and tests them, the second for rebound threads.Verification
refs/t3holds, the default branch, a deleted owner, and a holding ref deleted during measurement.vp run check:changedpassed format, lint, knip, graphql, the typecheck of 3 packages, and 10 test files with 793 tests.gc. Each defect was rerun before it was fixed, the reviewers' repros pass on this head except the mid-rebase case above, and removing each fix makes a test fail.Claude Fable 5.1 directed this work through T3 Code. Claude Opus 5.5 wrote the code. Claude Sonnet 5.5, gpt-6.1-sol, gpt-6-astra, GPT-6.1-Sol[1m], and DeepSeek-V4.1-Flash[1m] did research and design review.
🤖 Generated with Claude Code