[Bug]: Switching thread branch to a worktree mid-turn silently kills the running Claude session#14245

opened25d agobyBbooklet-mikolajczak

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/web

Steps to reproduce

  1. Start a thread (Claude provider) in a repo that has extra git worktrees (created outside T3, e.g. git worktree add ../wt-feature feature).
  2. Send a prompt that produces a long-running turn.
  3. While the turn is running, open the branch picker in the composer toolbar and select feature (the branch that is checked out in the other worktree). The goal is just to look at its diff in the Diff panel.
  4. Watch the thread.

Expected behavior

The running turn is not killed silently. Either:

  • the branch picker is disabled while the session is starting/running, or
  • the user is asked to confirm and is told that the current turn will be interrupted.

Actual behavior

  • The streamed output stops. There is no prompt or warning before the switch, and nothing in the thread explains that the turn was killed by the branch change.
  • The Claude process is terminated and the turn ends as interrupted ("Session stopped.").
  • Nothing happens until the user sends another message (e.g. .). Then a new session is started in the new worktree and resumed from the stored resume cursor. This looks like the stream "came back", but it is a new turn, and the interrupted turn's work may be left half done.

Code path (commit d2c9281b)

  • apps/web/src/components/BranchToolbarBranchSelector.tsx:176-194: setThreadBranch calls stopThreadSession whenever worktreePath !== activeWorktreePath, without checking whether a turn is running:
    ts
    1if (serverSession && worktreePath !== activeWorktreePath) {
    2 void stopThreadSession({ environmentId, input: { threadId: activeThreadId } });
    3}
  • BranchToolbar.logic.ts:262-289: the worktree path changes when the selected ref already has a worktree (reuseExistingWorktree) or when the default branch is selected from a worktree.
  • BranchToolbarBranchSelector.tsx:813: the trigger is disabled only by isInitialBranchesLoadPending || isBranchActionPending, not by session status. envLocked (ChatView.tsx:2626-2630) locks the env/worktree mode selectors but not the branch picker, even though BranchToolbar.tsx:553 says started server threads have their workspace pinned.
  • apps/server/src/provider/Layers/ClaudeAdapter.ts:4259-4360: stopSessionInternal terminates the process and calls completeTurn(context, "interrupted", "Session stopped.").
  • ProviderCommandReactor.ts:842 + ProviderService.ts:1466-1470: the next thread.turn.start starts a new session in the new cwd using the persisted resume cursor.

Suggested fix

Disable the branch picker (or at least ref selections that change worktreePath) while the session is starting/running. Alternatively, show a confirmation that interrupts the turn explicitly and leaves a visible "interrupted by branch switch" marker in the thread.

Impact

Major degradation or frequent failure. Users switch branches just to inspect a diff and lose the running turn with no feedback.

Version or commit

0.0.43-nightly.20260929.2428 (AppImage). Code references from main at d2c9281b.

Environment

Linux (Ubuntu), desktop app, Claude Code provider.

Workaround

Do not change the branch while a turn is running, or view the other worktree's diff outside T3.

Triage

Confirmed on current main (d2c9281b, the commit this report cites). No duplicate issue. The nightly you are on matches that tree, so this is not fixed in a newer build.

Selecting a ref that already has a worktree is a workspace retarget, not a read-only diff view. resolveBranchSelectionTarget sets reuseExistingWorktree and points nextWorktreePath at that checkout. selectBranch then calls setThreadBranch immediately, with no checkout. setThreadBranch calls thread.session.stop whenever a session record exists and the worktree path changes. It does not look at session status and does not confirm.

The branch trigger is disabled only while refs are loading or a branch action is pending. envLocked locks the environment and local/worktree mode once the thread has messages or a non-stopped session, and the toolbar comment says a started thread’s workspace is pinned. That lock is not applied to the branch picker. envLocked there only blocks picking a worktree base before a worktree exists.

The stop itself is deliberate: the provider process is bound to the old cwd, and the Diff panel follows thread.worktreePath. What is missing is a guard. The process is killed with no prompt, and the thread only gets the generic interrupted turn. For Claude that text is Session stopped. from ClaudeAdapter.stopSessionInternal. The client stop is not Claude-specific.

The same stop also runs for any other worktree-path change, including choosing the default branch while the thread is in a secondary worktree (nextWorktreePath becomes null). Choosing a branch that is not checked out elsewhere stays in the current worktree, runs switchRef, and does not stop the session. That checkout-under-a-running-turn case is separate from this report.

After the stop, the session row remains with status stopped, so nothing resumes on its own. The next message takes the fresh startProviderSession path in ProviderCommandReactor. The cwd comes from the updated thread.worktreePath, and ProviderService.startSession fills in the persisted resume cursor. That is a new turn in the other worktree, not the interrupted turn continuing.

Stopping the process on a real workspace change should stay. The picker should not be able to do that silently while the session is starting or running: either disable selections that change worktreePath, or confirm that the turn will be interrupted.

Synced from GitHub

Lol I am having the same issue mehn I was wondering why lol, I hope this get's solved soon sigh!

Synced from GitHub

Also hitting this on macOS, desktop app 0.0.46-nightly.20261014.3118 (latest nightly), Claude Code 2.1.300. If I switch branches from the T3 Code UI while a Claude turn is running, the turn dies. On the current nightly it doesn't show as a quiet Session stopped. interrupt anymore. It shows as a provider error instead (the same error message as #16000):

The provider event stream closed unexpectedly. Retry the turn; if it keeps failing, check the provider and server logs.

Looking forward to #15255 landing.

Reproduced in the Linux web dev app on main b5a226e49c: a fixture repository had master and an externally created feature worktree. While sleep 120 ran, selecting feature moved the thread without confirmation and ended the turn. Claude Opus 5.5 / Claude CLI 2.1.303 showed “The provider event stream closed unexpectedly”; Codex showed “Run interrupted by user.”

Recordings: Claude branch selection and provider error, Codex branch selection and interrupt.

The existing maintainer-rebuilt fix #15255 covers this issue and has 31 successful checks at its published head. No competing PR opened. Electron desktop was not verified in this VM. Issue remains open pending landing.