Windows: isolation-worktree safety check rejects valid worktree due to case-sensitive drive-letter comparison
Environment
- OS: Windows 11 Pro, build 10.0.26200.9278
- Editor: Cursor (VS Code fork), extension
anthropic.claude-code2.1.258 (win32-x64) - Git: git version 2.54.0.windows.1 (Git for Windows / MSYS)
- Repo opened via a
.code-workspacefile on a local NTFS drive (D:\...)
What happened
Running a workflow that uses agent(..., {isolation: 'worktree'}) (also reproduced via the orchestrate-spec pattern from the docs) fails on the very first isolated-worktree agent with:
Refusing to use d:\git_checkout\JTLDatevBridge\.claude\worktrees\wf_7ead4c95-9ec-4 as an isolation worktree:
git resolves its working tree to D:/git_checkout/JTLDatevBridge/.claude/worktrees/wf_7ead4c95-9ec-4
(a core.worktree redirect, or a checkout discovered above it), so commands run there would write outside
the worktree. Remove the redirect, restore the worktree's own .git, or recreate the worktree, then retry.
The worktree itself is completely healthy — manually verified:
.gitfile in the worktree correctly points toD:/git_checkout/JTLDatevBridge/.git/worktrees/wf_7ead4c95-9ec-4commondiris../..git -C <worktree> rev-parse --show-toplevelreturns the worktree's own pathgit -C <worktree> statusworks normally, no redirect, no core.worktree override anywhere (checked local/global/system config)
Root cause
This is a case-only string mismatch between two representations of the same path, not an actual worktree/redirect problem:
- The path the safety check is refusing (
d:\git_checkout\JTLDatevBridge\...) — lowercase drive letter — comes from the workspace context. VS Code (and forks like Cursor) always canonicalizefile://URIs with a lowercase drive letter. Confirmed by inspecting the stored workspace URI:file:///d%3A/git_checkout/JTLDatevBridge/JTLDatevBridge.code-workspace. - The path git reports (
D:/git_checkout/JTLDatevBridge/...) — uppercase drive letter — is git-for-windows'/MSYS's own normalization. Confirmed viapwd -Wandgit rev-parse --show-toplevel, both returnD:/....
The isolation-worktree safety check appears to compare these two path strings with a case-sensitive equality (or similar) check instead of normalizing both sides (e.g. lowercasing both, or resolving both through the same realpath/native path API) before comparing. On NTFS, drive letters and paths are case-insensitive, so this mismatch is spurious — but it will reproduce on every Windows machine where VS Code/Cursor's default lowercase-URI convention meets git-for-windows' uppercase normalization, i.e. essentially all default Windows + VS Code/Cursor + git-for-windows setups.
Impact
isolation: 'worktree' (used by agent() calls and the general worktree-isolation pattern used by e.g. orchestrate-spec-style workflows) is currently unusable on native Windows — every isolated-worktree agent call fails immediately at worktree-safety-check time, before any actual work happens.
Suggested fix
Normalize both sides of the comparison before checking — e.g. lowercase the drive letter on win32 (or use fs.realpathSync.native) for both the expected worktree path and the path git reports, rather than a raw string/=== comparison.
Where I looked
I could not find any settings-schema override (worktree.* in claude-code-settings.schema.json) or env var to opt out of this specific check — worktree.location exists but per its own description is "not read yet" by the CLI/agent-isolation path. The check itself appears to live in the compiled resources/native-binary/claude.exe, not in extension.js, so I could not patch around it locally.
Happy to provide the full workflow script / journal.jsonl from the failing run if useful.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Bisection datapoint: this reproduces deterministically on current stable 2.1.236 (Windows 11, git 2.55.0.windows.4).
Method
.claude/worktrees/.claude -p) with the process working directory set to the lowercase-drive form of the repo path (c:\Users\...\repo) — the form was verified preserved in the child'sprocess.cwd().isolation: "worktree".Result — verbatim incident signature
The refused path and the resolved path denote the same location — they differ only in drive-letter case and separators. For this instance the parenthetical guesses are directly falsifiable:
.gitis a standard gitfile:gitdir: C:/.../repo/.git/worktrees/agent-a75f651045fa09cc5(same repo)gitdirback-pointer is consistent with itcore.worktreeis unsetSo the validator appears to compare the path form it holds (lowercase drive, backslashes) against
git rev-parse --show-topleveloutput (uppercase drive, forward slashes) with strict string equality on win32.Additional observations on 2.1.236
locked—locked claude agent agent-<id> (pid <session pid>)persists ingit worktree listafter the session exits.isolation: "remote"silently stages a local worktree: it is an accepted enum value on this build, and it went through the same local provisioning — identical refusal with a secondagent-<id>— without any error naming the mode actually used.Version window (for bisecting): 2.1.41 has no
isolationparameter at all; 2.1.86 did not reproduce under the same reconstructed trigger (there, the agent worktree path appeared to be derived from the resolved main working tree rather than the raw cwd string); 2.1.236 reproduces. The 2.1.238 / 2.1.257 / 2.1.259 changelog entries about worktree-isolation refusals concern Bash-command gating inside isolated sessions, not this pre-flight provisioning validation.Workaround: after a refusal, remove the leftovers —
git worktree unlock <path>+git worktree remove --force <path>+git worktree prune.Bisection datapoint: 2.1.141 does NOT reproduce the reconstructed trigger (Windows 11).
Method: spawned
claude -p(2.1.141) via nodespawnSyncwith literal cwdd:\Dev\roo-extensions— lowercase-drive form verified preserved in the child'sprocess.cwd()— then invoked the Task tool withisolation: "worktree".Result: the worktree provisioned and validated with no refusal; the sub-agent ran inside
.claude/worktrees/agent-acf9e64794c902166, and after completion both the worktree and itsworktree-agent-*branch were fully cleaned up (nolockedresidue ingit worktree list). An uppercase-drive cwd session on the same build also passes, as does a direct in-session Task invocation.So the regression window narrows to (2.1.141, 2.1.236] — the previously known unaffected lower bound was 2.1.86.
Bisection datapoint: still reproduces on 2.1.277 (dist-tag
latest, Windows 11, git 2.55.0.windows.4, 2026-09-18).Method — identical to the previous datapoints: scratch repo with two pre-existing worktrees under
.claude/worktrees/, headlessclaude -pspawned with the lowercase-drive form of the repo path as process working directory (form verified preserved in the child'sprocess.cwd()), then a Task call withisolation: "worktree".Results on 2.1.277
c:\Users\…\agent-ab3b6c2b3ec1747e6vs resolvedC:/Users/…/agent-ab3b6c2b3ec1747e6: same location, drive-letter case and separators only. The worktree's.gitis a standard gitfile pointing into the same repo's.git/worktrees/…, nocore.worktreeset — so the parenthetical guesses in the message are falsified for this instance, as in earlier reports.run_in_background: false). For context, on 2.1.274 we observed the harness retry with a normalized cwd and the agent then starting (reported by another machine in our fleet on 2026-09-17); whether that retry is version-gated or flag-gated is not resolved from these two observations.locked claude agent agent-… (pid …)entry with a dead pid behind (3 calls → 3 locked orphans, all pids dead at inspection). No cleanup on the failure path.isolation: "remote"still silently provisions a LOCAL worktree on this build — same refusal for a locally createdagent-*worktree, with no error naming the mode actually used.Bisection so far: 2.1.86 no-repro · 2.1.141 no-repro · 2.1.236 repro (terminal) · 2.1.269 repro (terminal) · 2.1.274 repro→retry-success (orphan still left) · 2.1.277 repro (terminal). The 2.1.275–2.1.277 CHANGELOG has no entry matching this validator (the worktree-related fixes shipped in 2.1.274 concern Bash-command confinement and worktree removal safety, not pre-flight provisioning validation).