You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: Storage cleanup deletes git worktrees nested inside a thread worktree, including uncommitted work #23866
I searched existing issues and did not find a duplicate.
I included enough detail to reproduce or investigate the problem.
Area
apps/server
Steps to reproduce
Use a project whose root is a git repo that gitignores a folder where another repo lives, for example /backend/ holding a separate backend clone. This is a common "workspace repo" layout: agent instructions and skills sit at the root, and the product repos are gitignored inside it.
Start a thread in a new worktree.
Inside that thread worktree, have the agent create a worktree of the other repo at the ignored path (git -C <root>/backend worktree add <thread-worktree>/backend -b fix/x), and leave an uncommitted file in it.
Leave the thread idle, with storage cleanup set to worktreeUnchanged: true (or delete the thread with worktreeOnDelete), and run storage cleanup.
A git-only repro of the underlying removal:
git init -q umb &&printf'/backend/\n'> umb/.gitignore && git -C umb add .&& git -C umb commit -qm base
git init -q be && touch be/f && git -C be add .&& git -C be commit -qm base
git -C umb worktree add -q ../task -b task/x
git -C be worktree add -q ../task/backend -b fix/y
echo wip > task/backend/wip.txt
git -C task status --porcelain # empty: backend/ is ignored
git -C umb worktree remove ../task # succeeds; task/backend/wip.txt is gone
git -C be worktree list # task/backend is now "prunable"
Expected behavior
Cleanup keeps a worktree that contains another git worktree or repository, the same way it already keeps one that contains a project checkout.
Actual behavior
The nested worktree is deleted along with its uncommitted files, and the other repo is left with a stale (prunable) worktree entry. Its commits survive, because they're stored in the other repo.
Impact
Uncommitted work is lost silently. An umbrella worktree never gets commits of its own, so with worktreeUnchanged on it is cleaned up as soon as the thread is idle and its session has ended.
Cause (in apps/server/src/storageCleanup.ts):
localChangesReason relies on git status, which doesn't report ignored paths.
containsProjectRoot only protects checkouts of registered T3 projects.
removeLinkedWorktree then runs git worktree remove, which deletes ignored nested repos recursively.
With worktreeKeepWhen: "tracked-changes", the git clean -ffdx before removal also deletes nested repos (the double -f).
worktreeKeepWhen: "any-local-files" avoids it, but it isn't the default.
Suggested fix: before removing a worktree, refuse if it contains any .git file or folder other than its own at the root, or any path registered in another repo's git worktree list.
Version or commit
0.1.2-nightly.20261022.3541 (source read at 2c8bdf61)
Environment
Linux, git 2.53.0, Node 22.23.2, Claude Code 2.1.308
Logs or stack traces
None; the git-only repro above shows the removal.
Workaround
Keep other repos' worktrees outside T3's worktrees folder, or set worktreeKeepWhen: "any-local-files" for the project.
Was the first sign a backend worktree showing up as prunable in git worktree list?
Every single step here looks routine on its own. git status comes back empty because backend/ is ignored, worktreeUnchanged reads that as nothing to lose, and then git worktree remove, or the git clean -ffdx on the tracked-changes path, takes the nested repo and its uncommitted files with it, all without one warning. Nobody would flag a cleanup like that as risky, which is exactly why the loss is silent and hard to explain after the fact. That kind of quiet deletion is what I spend most of my time on now, being able to trace back what a job saw and why it decided a folder was safe to drop. Refusing whenever there is any .git entry below the root or a path in another repo's worktree list sounds like the right guard to me. How long had umbrella worktrees been getting cleaned up before you dug into storageCleanup.ts?
Activity
samvallad33 commentedon Oct 22, 2026
Hi Kieron,
Was the first sign a backend worktree showing up as prunable in git worktree list?
Every single step here looks routine on its own. git status comes back empty because backend/ is ignored, worktreeUnchanged reads that as nothing to lose, and then git worktree remove, or the git clean -ffdx on the tracked-changes path, takes the nested repo and its uncommitted files with it, all without one warning. Nobody would flag a cleanup like that as risky, which is exactly why the loss is silent and hard to explain after the fact. That kind of quiet deletion is what I spend most of my time on now, being able to trace back what a job saw and why it decided a folder was safe to drop. Refusing whenever there is any .git entry below the root or a path in another repo's worktree list sounds like the right guard to me. How long had umbrella worktrees been getting cleaned up before you dug into storageCleanup.ts?
Sam