Thread resumes in a worktree folder without .git instead of recreating the checkout#24390

opened3d agobySszemroda

What happened

Earlier storage-cleanup removals on Windows left the worktree folder in place with only node_modules (sometimes .next) inside, with no .git and no tracked files. When a thread pointing at such a folder runs again, T3 Code sees that the folder exists and skips checkout recovery, so the agent works in a directory with no checkout.

Expected: a managed worktree path that is not a valid checkout is recovered (cleared and recreated from the thread's branch), the same way a missing folder is.

Diagnosis

  • On this machine, 75 folders under ~\.t3\worktrees\<project>\ were in this state. Most were removed in bursts on 2026-10-16 and 2026-10-21 on 0.1.1. Their node_modules are pnpm layouts full of NTFS junctions. With Git for Windows ≥ 2.54, git worktree remove skips junctions and leaves them and their parent folders behind, which is exactly what apps/server/src/vcs/worktreeRemoval.ts describes. #21099 fixed the removal for new cleanups, but nothing repairs folders that are already broken.
  • apps/server/src/orchestration-v2/ProviderTurnStartService.ts:476-480 (v0.1.2; same on main at ~491-493) recreates the worktree only when !fileSystem.exists(worktreePath). A folder holding only node_modules passes the existence check, so pruneWorktrees + createWorktree never run.
  • The same folders also make every storage-cleanup sweep log a failure. storageCleanup.ts:803 calls fs.stat(path.join(worktreePath, ".git")), which throws ENOENT instead of returning keep("not a linked worktree"). In the retained traces, the same thread failed this way at 10:47 and 11:48 (hourly).

Suggested direction: in ProviderTurnStartService, treat a managed worktree path without a valid .git link (or one not registered in git worktree list) like a missing one. In storageCleanup.ts, handle a missing .git as a "kept" outcome, not an error.

Steps to reproduce

Deterministic, without depending on Git's junction behaviour:

  1. Create a worktree thread in a pnpm project and let setup install node_modules.
  2. Stop the thread, then delete everything in the worktree folder except node_modules (including the .git file). Optionally run git worktree prune in the main repo.
  3. Send a new message to the thread.
  4. Expected: "provider turn start recreating missing worktree" and a fresh checkout of the thread's branch. Actual (from source): the existence check passes, recovery is skipped, and the turn runs in the gutted folder.
  5. Separately, the next storage-cleanup sweep logs "storage cleanup failed for worktree" with NotFound … stat …\.git.

Natural repro on 0.1.1: Windows, Git for Windows ≥ 2.54, pnpm project, and any worktree cleanup rule removing the worktree.

Version

0.1.2 (recovery gap and sweep error; also on main). The leftover folders themselves were created on 0.1.1.

Environment

Windows 11 (10.0.26200) x64, Node v26.12.1, Git 2.55.0.windows.3, pnpm projects, desktop app with local server

Evidence

shell
1# leftover folder (one of 75)
2~\.t3\worktrees\zesty-explorer\t3code-df890ade\ contents: node_modules\ (no .git; mtime 2026-10-16 11:35)
3node_modules\clsx Junction -> ...\t3code-df890ade\node_modules\.pnpm\clsx@2.1.1\node_modules\clsx
4
5# server.trace.ndjson, StorageCleanup.cleanWorktrees, 2026-10-24 10:47 and 11:48
6"storage cleanup failed for worktree", threadId 6ba3d897-8d26-4c66-b75a-17d412d03866
7 reason: { _tag: "NotFound", module: "FileSystem", method: "stat",
8 pathOrDescriptor: "~\\.t3\\worktrees\\zesty-explorer\\t3code-df890ade\\.git" }

Related issues

#21374 covers a missing worktree directory where the re-create fails with branch_checked_out_in_worktree; this report is the case where the directory still exists, so re-create never starts. #19124 is about turns failing after merge removes the worktree and branch. #21099 (PR) fixed how the folders get left behind, not recovery from them.

Fix applied or workaround

With the user's approval, deleted the 75 folders whose only top-level entries were node_modules/.next and that had no .git. Deletion used Node's fs.rmSync, which unlinks junctions without following them; no junction pointed outside its own folder. Threads using those paths should now go through the normal missing-worktree recreation. Folders with any other content were left alone.

Filed by

Claude Code (Claude Opus 5.5, claude-opus-5-5) via t3 triage

Synced from GitHub

Note

🤖 Claude Opus 5.5 responding on behalf of @phillip-hirsch

The same cleanup failure happened on this Windows machine from a different cause. An agent cleaned up its subagents' worktrees by running git worktree remove itself. Git removed the checkouts and unregistered the worktrees, but two folders failed to delete with error: failed to delete '…\t3-0c653d44': Permission denied. The subagents probably still had them open. Both folders were left empty, with no node_modules. Every storage cleanup run after that failed with NotFound: FileSystem.stat (…\t3-0c653d44\.git). I deleted the empty folders by hand. With #24398, cleanup would keep them as "not a linked worktree" instead of failing.