remote-control --spawn worktree: default pre-created session dies with 404 and leaves a stale .claude/worktrees that permanently breaks all later spawns (EEXIST, Windows)

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 0 comments · opened Jul 28, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

On Windows, claude remote-control --spawn worktree bricks itself on first use, and stays bricked until you manually delete .claude/worktrees and strip a ReadOnly attribute off .git/worktrees/<name>.

The chain (all observed in one 30-minute session, v2.1.220):

  1. The default-on pre-created session dies with a 404. --create-session-in-dir is on by default, so the server pre-creates one session at startup. That session immediately failed with RemoteIO: transport closed permanently (code 404) and the harness explicitly kept its worktree (· session crashed). The server process itself was fine — it printed Ready, the session URL and the QR code, so nothing signals that anything needs cleaning up.
  1. The leftover directory then permanently breaks every subsequent worktree spawn. On the next run, creating a session from the mobile app fails with EEXIST: file already exists, mkdir '...\.claude\worktrees' — the parent directory, not the session's own subdirectory. This is the same non-idempotent mkdir root cause as #70313 (--worktree flag) and the subagent-isolation reports (#39680, #51023, #54706, #64519), now reproducing on a third surface: remote-control spawn.
  1. From the phone, this failure is invisible. The session gets created server-side and appears in the session list at claude.ai/code, but it has no local worktree, so it simply never responds. No error is surfaced to the remote client. You just get a session that is silently dead.
  1. Recovery is harder than it should be. git worktree remove --force deregistered the worktree but could not delete the files (repeated Deletion of directory ... failed. Should I try again? (y/n) prompts). git worktree prune then failed with error: failed to delete '.git/worktrees/<name>': Permission denied, because the kept worktree's admin directories carry the ReadOnly attribute (cf. #37306). Recovery required clearing the attribute and deleting .git/worktrees/<name> by hand.

Once .claude/worktrees was fully removed, worktree spawn worked on the first try: Capacity: 1/32, worktree registered, and a prompt sent from the mobile app was answered normally. So the feature itself is fine — it is the leftover state that is fatal.

What Should Happen?

  • Creating the worktree parent should be idempotent: mkdir(..., { recursive: true }) (or an existence check) so a pre-existing .claude/worktrees is not fatal. This one-line class of fix has now been reported for --worktree, subagent isolation, and remote-control spawn.
  • A crashed pre-created session should not leave state that permanently disables the feature. Either clean up the worktree it kept, or tolerate the leftover on the next spawn.
  • A worktree that is deliberately kept for post-mortem should not be left with ReadOnly admin dirs that make git worktree prune fail.
  • When a spawn fails locally, the remote client should be told. A session that can never respond should not sit in the session list looking healthy.

Error Messages/Logs

Run 1 — defaults (pre-created session on). Server reports Ready regardless:

PS C:\Users\<user>\Documents\GitHub\<repo>> claude remote-control --spawn worktree

Enable Remote Control? (y/n) y

[18:52:22] Session failed: RemoteIO: transport closed permanently (code 404) cse_<redacted>
[18:52:22] kept worktree C:\Users\<user>\Documents\GitHub\<repo>\.claude\worktrees\bridge-cse_<redacted> · session crashed

·✔︎· Ready · <repo>
    Capacity: 0/32 · New sessions will be created in an isolated worktree

Run 2 — leftover directory present; spawning a session from the mobile app:

PS C:\Users\<user>\Documents\GitHub\<repo>> claude remote-control --spawn worktree --no-create-session-in-dir --verbose
Remote Control v2.1.220
Spawn mode: worktree
Max concurrent sessions: 32
Environment ID: env_<redacted>

[19:07:32] Error: Failed to create worktree for session cse_<redacted>: EEXIST: file already exists, mkdir 'C:\Users\<user>\Documents\GitHub\<repo>\.claude\worktrees'

The session exists at claude.ai/code/session_<redacted> but never responds.

Cleanup blocked by ReadOnly admin dirs:

PS> git worktree remove --force .claude/worktrees/bridge-cse_<redacted>
Deletion of directory '.../.claude/worktrees/bridge-cse_<redacted>/.github/workflows' failed. Should I try again? (y/n) y
Deletion of directory '.../.claude/worktrees/bridge-cse_<redacted>/.github/workflows' failed. Should I try again? (y/n) y
... (repeats indefinitely)

PS> git worktree prune
error: failed to delete '.git/worktrees/bridge-cse_<redacted>': Permission denied

PS> Get-ChildItem .git\worktrees -Recurse -Force | Select-Object Name, Attributes
Name                        Attributes
----                        ----------
bridge-cse_<redacted>       ReadOnly, Directory
logs                        ReadOnly, Directory
refs                        ReadOnly, Directory
ORIG_HEAD                   Archive

Run 3 — after Remove-Item -Recurse -Force .claude\worktrees and manual removal of .git\worktrees\<name>:

[19:18:03] Session started: "Session cse_<redacted>" (cse_<redacted>)

·✔︎· Connected · <repo>
    Capacity: 1/32 · New sessions will be created in an isolated worktree

Worktree registered correctly and the mobile-sent prompt was answered.

Steps to Reproduce

  1. On Windows, cd into a git repo and run claude remote-control --spawn worktree (defaults, i.e. --create-session-in-dir on).
  2. Observe the pre-created session fail with transport closed permanently (code 404) and keep its worktree under .claude/worktrees/. The server still reports Ready.
  3. Stop the server. Restart it (with or without --no-create-session-in-dir).
  4. From the Claude mobile app or claude.ai/code, create a session in that environment.
  5. ❌ Terminal: Failed to create worktree for session ...: EEXIST: file already exists, mkdir '...\.claude\worktrees'. The session appears in the session list but never responds.
  6. Every later attempt fails identically until .claude/worktrees is deleted.

Step 2's 404 is what seeds the leftover in the wild, but step 5 reproduces from any pre-existing .claude/worktrees directory — for example one left behind by the git worktree remove exit-255 lock problem described in #76590.

Claude Model

Sonnet 5

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.220 (Claude Code), native install

Platform

claude.ai (subscription OAuth login)

Operating System

Windows 11 (10.0.26200), NTFS

Terminal/Shell

PowerShell 7.6.4 / Windows Terminal

Additional Information

Related — same non-idempotent mkdir('.claude/worktrees') root cause on other surfaces:

  • #70313 — --worktree flag, EEXIST on second worktree creation (open)
  • #39680, #51023, #54706, #64519 — Agent tool isolation: "worktree" / subagent worktrees (closed; #64519 notes the recurrence explicitly)
  • #37306 — Windows EEXIST/ReadOnly-attribute interaction
  • #76590 — Windows: stale non-empty .claude/worktrees reused by desktop session creation, and the git worktree remove exit-255 lock loop that produces leftovers

Notes:

  • --verbose on remote-control server mode floods the terminal with the raw NDJSON protocol stream (each control_response carries the full slash-command, agent and model catalogs) and re-renders the QR block on every event, which makes diagnosing anything in that mode impractical. The per-session debug log at %TEMP%\claude\bridge-session-<id>.log is far more usable; consider not mirroring the full protocol stream to stdout.
  • --spawn worktree also requires being inside a git repository, which is easy to miss: starting the server from a non-repo directory (e.g. the home directory) still prints Ready, and only fails later at spawn time.

View original on GitHub ↗