[Bug]: Thread can't start a turn after its worktree and branch are removed by a merge#19124

opened12d agobyMmirsella

Area: apps/server

Steps to reproduce

  1. Run a thread in a worktree on a branch (Codex provider).
  2. Land the work the way the agent does it: fast-forward the branch onto main, git worktree remove <path>, git branch -d <branch>.
  3. Send any message in the thread.

Expected behavior

The turn starts, or at worst fails with an error that says the checkout is missing and offers a way to recover it.

Actual behavior

The turn fails a few seconds after it is requested (requested 17:09:27, failed 17:09:30), and the UI shows no error. Every later message fails the same way, so the thread stays stuck.

The recovery from #7839 only covers a missing worktree while the branch still exists: thread.turn.start runs git worktree prune and then createWorktree({ refName: branch }). With the branch deleted, git worktree add <path> <branch> fails, the failure is logged as a warning, and the turn continues into the provider. The Codex session then can't resume because its recorded cwd is the deleted worktree path and resume requires a matching cwd. Falling back to the project's default branch or the workspace root HEAD when the thread branch no longer resolves would recover this case.

Impact

Blocks work completely

Version or commit

0.0.46-nightly.20261013.3053

Environment

Linux, local t3 serve, Codex provider (codex app-server 0.162.1). The thread's worktree was under ~/.t3/worktrees/<project>/<id>.

Logs or stack traces

The run row records no error for the failed run; the server only warns during the recreate attempt.

Workaround

Recreate the branch at the merged commit and re-add the worktree at the exact original path:

1git branch <branch> main
2git worktree add <original worktree path> <branch>

Sending a message then works again with the Codex session intact, because the recorded cwd exists again.

Synced from GitHub

Same on desktop app 0.1.0 (81493efb71), macOS 14.8.8, Claude provider, so it is not Codex-specific. In 0.1.0 the turn fails with a visible error: "Provider session failed to open: This thread's workspace folder no longer exists or is not a directory: ~/.t3/worktrees/demo-repo/t3-e59cbb42. Restore the folder at this path before retrying."

Two UI findings on top of the server side:

  1. Once the app refreshes git status for the missing folder (an app restart does it, a window reload does not), the thread menu drops the branch picker, PR, commit and Changes rows and only offers "Initialize Git". That fails with "VCS process failed in GitVcsDriver.initRepository: git (<path>) exited with 128", because the server runs git -C <path> init and git exits 128 when the directory does not exist (apps/web/src/components/GitActionsControl.tsx:1709, apps/server/src/vcs/GitVcsDriver.ts:529 at v0.1.0).
  2. Before that refresh, the branch picker is still visible, and picking main there recovers the thread: it moves to the project's local checkout and the next message runs. So a recovery path already exists. It is just hidden once git status reports isRepo: false for the missing folder.

Keeping the branch picker for a thread whose worktree folder is gone, and not offering "Initialize Git" for a folder that does not exist, would give a way out even without the default-branch fallback suggested above.

Logs from 0.1.0:

1provider turn start recreating missing worktree
2 { threadId: "bd3fed77-...", worktreePath: "~/.t3/worktrees/demo-repo/t3-e59cbb42", branch: "t3/e59cbb42" }
3provider turn start failed to recreate worktree
4 GitCommandError: Git command failed in GitVcsDriver.createWorktree (~/Documents/demo-repo): git worktree add failed

After an app restart: the turn fails and the thread menu only offers "Initialize Git". Turn fails and the thread menu only offers Initialize Git

Clicking "Initialize Git": Initialize Git fails with exit 128

Synced from GitHub

Confirmed on macOS with the Claude provider. This happened to me last night after an agent merged a PR and cleaned up its builds, simulators, worktree and branch.

The recorded commands show it explicitly ran git worktree remove --force <its-own-worktree> and git branch -D <its-branch>. The shell then reset its cwd to the main checkout, but T3 kept the thread bound to the deleted worktree under ~/.t3/worktrees/<project>/<worktree>.

My next message asked it to build and release to internal TestFlight. About two seconds later, the turn failed with:

text
1Provider session failed to open
2This thread's workspace folder no longer exists or is not a directory: <original worktree path>. Restore the folder at this path before retrying.

Another message failed the same way. The stored thread binding still points at the missing folder.

So this also happens with agent-driven cleanup outside T3's cleanup UI. Being able to reattach the existing thread to the main checkout would have let me continue without starting a new conversation.

Reproduced on Linux in the web dev app at 70e5e384da, using Codex 0.163.0 and GPT-6.1-Sol. A first turn completed in a managed worktree. After an empty commit, git merge --ff-only, git worktree remove, and git branch -d, two later messages both failed. The server logged git worktree add failed (ref_not_found) followed by ProviderWorkspaceMissingError.

Main now shows the missing-folder error in the UI; the deleted-branch recovery failure remains.

Failed turn after worktree and branch cleanup

Synced from GitHub

I hit this on macOS with T3 0.1.2, using Codex and GPT-6.1-Sol. Settings showed the app was up to date.

My agent finished a feature, squash-merged the PR into main, then removed its worktree and branch because my workflow instructions require cleanup after merging. The next message failed. T3 still pointed at the deleted checkout, and the provider session couldn't open. The error asked me to restore the folder at its original path before retrying.

I checked the repo. The folder was gone, git worktree list no longer listed it, and the feature branch was missing both locally and on the remote. Local main matched remote main at the squash-merge commit.

t3_thread_read still showed the old path and branch, with no active run. For the same thread, t3_worktree_list returned isRepo: false and no refs.

To recover, I recreated the original branch from merged main and added a worktree at the exact path T3 had saved:

sh
1git worktree add <original-path> -b <original-branch> <current-main-sha>

The checkout was clean. T3 recognized it again, and a new message in the original thread successfully resumed the provider session and got a response.

I'd like a way to recover from this inside T3. In this case the feature had already landed through a squash merge, so recovery needed to account for that rather than blindly restoring the old feature head.

Synced from GitHub

Also hit on Linux with T3 0.1.0 (local t3 serve, Claude provider). One extra variant that the current fix PRs (#23132, #18359) would recover from, but which the recovery could handle better:

The recorded branch went stale because the agent renamed it in-session. T3 created the worktree on t3/<slug>. Following the user's workflow instructions ("rename tool-created branches before the first push"), the agent ran git branch -m <type>/<slug> inside the worktree, later switched to a new branch with git switch -c, and deleted the renamed branch after its PR merged. The thread record kept branch: t3/<slug>, because T3 only propagates its own background rename (#20079), not a rename done by the agent. Later the folder was removed by a separate cleanup (not by T3, whose storage cleanup is off on this install, and not by the thread's agent). The next /compact logged:

text
1provider turn start recreating missing worktree
2provider turn start failed to recreate worktree: GitCommandError ... git worktree add failed
3ProviderWorkspaceMissingError: This thread's workspace folder no longer exists or is not a directory: ~/.t3/worktrees/<project>/t3-<id>. Restore the folder at this path before retrying.

So recovery looked for a ref that had disappeared at the rename, even though the branch the worktree last had checked out still existed locally.

Suggestions, in addition to the root-HEAD fallback:

  • Remember the branch the worktree last had checked out (T3 already reads it from git for status) and, on ref_not_found, try that branch before falling back to the project's HEAD.
  • Offer a "Rebind" action for a thread whose folder is gone: recreate at the same path from a chosen ref, or attach the thread to the project root. The same path matters for the Claude provider, which resolves its session transcript from the exact cwd.

On this install, 55 settled user threads point at missing worktree folders, 52 of them with no recorded branch left, so each would fail the same way if reopened.

Workaround, based on the 0.1.0 turn-start code: recreate only the recorded branch name from a suitable base, then send a message; the existing recovery re-adds the folder at the recorded path. Recreating the folder yourself with git worktree add -b <recorded branch> <recorded path> <base ref>, as described above, also works.

sh
1git -C <project root> branch <recorded branch> <base ref>