Windows: session creation reuses stale non-empty .claude/worktrees dir, skips worktree registration, and checks the session branch out in the PARENT repo (HEAD flip under concurrent sessions)

Open 💬 3 comments Opened Jul 11, 2026 by overseer-marketing

Environment

  • Windows 11 Pro (10.0.26200), NTFS
  • Claude Code desktop app (sessions created via the app's worktree affordance); claude --version reports 2.1.198
  • One repo, many concurrent desktop sessions, each assigned a dir under .claude/worktrees/<slug>/

Summary

When the desktop app creates (or resumes) a session whose designated worktree directory already exists and is non-empty, worktree registration is silently skipped — git worktree add refuses a non-empty dir at every force level — and the harness instead checks the session's branch out in the parent repository. Two consequences:

  1. The parent checkout's HEAD flips onto the new session's branch as a side-effect of session creation/resume, under any concurrent session or automation using that checkout.
  2. The new session runs with cwd = a plain, unregistered dir. Every git command resolves upward to the parent repo, while the session banner still claims "You are operating in a git worktree." The session is effectively un-isolated (a "ghost session").

Why stale dirs exist (the loop)

  • On Windows, git worktree remove for a closed session often exits 255: the app's file-watcher holds a lock, so git deregisters the worktree but cannot delete the folder. The leftover retains .claude/settings.local.json (the settings copy the app writes into each worktree).
  • The app later reuses such a leftover dir as the worktree target for a brand-new or resumed session (observed: one dir hosted five successive sessions over four days). The first session in a fresh dir gets a healthy registered worktree; every session reusing a leftover becomes a ghost.

Evidence (2026-07-09/10, one workspace)

  • Five parent-repo reflog entries checkout: moving from X to <new-session-branch> correlate 1:1, within seconds, with ghost session creations/resumes.
  • Every ghost session's cwd dir pre-existed its session by 1-4 days; every healthy session's dir was created fresh at registration time.
  • In the session list, every ghost session shows cwd-slug != branch-slug; healthy sessions match.
  • Concrete damage: at 15:56:03 a new session's creation flipped the parent's HEAD mid-operation of a live ghost session — the ghost's cherry-pick, aimed at its own branch, landed on the seconds-old other session's branch instead, stranding a commit (recovered from the reflog).
  • Healing observation: two sessions whose leftover dirs had been fully deleted re-registered healthy worktrees on resume; two others resumed into surviving leftovers, ghosted, and flipped the parent's HEAD.

Reproduction

  1. mkdir -p <repo>/.claude/worktrees/<slug>/.claude && echo '{}' > <repo>/.claude/worktrees/<slug>/.claude/settings.local.json
  2. Start/resume a desktop session that resolves to <slug> as its worktree dir.
  3. Observe: the slug is absent from git worktree list; the parent reflog gains a checkout: moving from <old> to <session-branch> entry stamped at session start; from inside the session, git rev-parse --show-toplevel returns the parent root and git status shows ../../..-prefixed paths.

Expected behavior (any of)

  • Refuse to start the session (or fall back to a fresh slug) when the target dir exists but is not a registered worktree;
  • Clear/recreate the dir, then run git worktree add normally;
  • Never fall back to checking the session's branch out in the parent repo — that mutates shared state that concurrent sessions and scheduled automation depend on.

Related but distinct

  • #45024 added deliberate reuse of an existing worktree for a branch — this report concerns reuse of an existing unregistered dir.
  • #61630 covers a worktree disappearing mid-run; here the worktree never exists in the first place.
  • #60624 was EnterWorktree-specific; this is the desktop session-creation path (EnterWorktree is never called).

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗