main-bash-cwd-persists-nested-worktree · v1.2.0 · 2026-07-08 · sha256 1fc2fa44b1b6605c
main-bash-cwd-persists-nested-worktree v1.2.0A
Immutable. This exact content is served forever at /api/v1/blob/1fc2fa44b1b6605c.
--- name: main-bash-cwd-persists-nested-worktree description: | Prevent and diagnose nested git worktrees created at the wrong filesystem path when orchestrating parallel work from the main Claude Code agent. Use when (1) you ran `git worktree add .claude/worktrees/<name> ...` from the main agent's Bash tool, (2) `git worktree list` shows the new worktree at an unexpected nested path like `.claude/worktrees/<previous-worktree>/.claude/worktrees/<name>` instead of the intended top-level location, (3) a subagent dispatched to the intended path reports "worktree path mismatch" or operates from a longer nested path. Root cause: the main agent's Bash tool **persists cwd between calls** (per its docstring: "The working directory persists between commands"). An earlier `cd /abs/path && cmd` changes the main shell's cwd; a later `git worktree add <relative-path> ...` resolves against that persisted cwd rather than the project root, silently creating nested layouts. This is the **inverse** of the subagent variant (subagent-bash-cd-wrong-worktree) — subagent shells reset per call, main shell doesn't. **Also covers the sibling case (v1.1): from the project ROOT, a `git worktree add ../<name> ...` escapes UP to the repo's PARENT dir (e.g. `/Users/x/Documents/<name>`), OUTSIDE the `.claude/worktrees/` convention — and a later Read/Edit/Write using the assumed `.claude/worktrees/<name>` absolute path fails with a MISLEADING "file does not exist" (the file exists, the worktree is just elsewhere).** **Also covers (v1.2): a drifted cwd makes `git diff A B -- <repo-relative-path>` return EMPTY while `git diff A B --stat` (no pathspec) shows the same file as changed — because pathspecs resolve relative to cwd but `--stat` reports against the repo root. Tell: a pathspec-filtered git command comes back empty yet `--stat` / `git show <rev>:<path>` disagree → suspect cwd drift, not the commit/branch/range.** author: Claude Code version: 1.2.0 date: 2026-07-02 disable-model-invocation: true --- # Main-agent Bash cwd persistence → nested worktrees at wrong path ## Problem You're orchestrating multiple parallel-dispatch subagents, each in its own git worktree. From the main agent's Bash tool you do: ```bash git worktree add .claude/worktrees/track-A origin/main -b feat/track-A # ... dispatch Track A subagent ... # ... investigate codebase ... cd /path/to/repo/.claude/worktrees/track-A && grep -rn "foo" src/ # persists cwd! # ... time passes, you dispatch Track B ... git worktree add .claude/worktrees/track-B origin/main -b feat/track-B ``` You expect `track-B` at `<repo>/.claude/worktrees/track-B`. Instead `git worktree list` reports it at `<repo>/.claude/worktrees/track-A/.claude/worktrees/track-B` — nested inside the Track A worktree. Branches, commits, and pushes still work (git resolves them via `.git/worktrees/<name>` metadata regardless of disk location), but: - Dispatched subagents pointed at the intended path will report "worktree path mismatch." - Cleanup (`git worktree remove .claude/worktrees/track-B`) from the project root won't find it. - Per-worktree caches, venvs, and uploads end up under the wrong parent directory. ## Context / Trigger conditions - Main agent runs more than one `git worktree add` in the same session. - Between worktree-add calls, the main agent ran a Bash command that included a `cd` to a path other than the project root. - The later `git worktree add` uses a **relative** path (e.g., `.claude/worktrees/<name>`). - Symptom: `git worktree list` shows nested paths, OR a subagent given the intended path can't find its worktree, OR `pwd` inside a subagent reports a longer-than-expected path. The Bash tool's own description says it: *"The working directory persists between commands, but shell state does not."* This is the opposite of subagent Bash, which resets cwd per call (see `subagent-bash-cd-wrong-worktree`). ## Solution Three reliable mitigations, in order of preference: 1. **Always pass an absolute path to `git worktree add`** from the main agent. ```bash git worktree add /Users/<user>/Documents/the-handover-repo/.claude/worktrees/track-B origin/main -b feat/track-B ``` This is the cleanest fix and is robust against any cwd drift. 2. **Reset cwd to project root** in the same chained call before any `git worktree add` with a relative path: ```bash cd /Users/<user>/Documents/the-handover-repo && git worktree add .claude/worktrees/track-B origin/main -b feat/track-B ``` 3. **Create ALL worktrees up-front in a single batch** at the start of the session, before any other Bash commands that might `cd`. When dispatching parallel subagent prompts, also bake an absolute-path verification step into each prompt: ``` First action: verify worktree path matches expectation. cd /Users/<user>/Documents/the-handover-repo/.claude/worktrees/<name> pwd # MUST end in <name>; STOP and report if it doesn't git rev-parse --abbrev-ref HEAD # MUST be feat/<name> ``` ## Verification After creating a worktree, immediately: ```bash git worktree list | grep <name> ``` The reported path must NOT be nested under another worktree. If it is, before any subagent dispatch: ```bash # Move it to the correct location (requires admin; or just remove + re-add) git worktree remove <wrong-nested-path> git worktree add /abs/path/to/.claude/worktrees/<name> origin/main -b feat/<name> ``` ## Example **What went wrong (Session 15, the-handover-repo):** ```bash # Main agent at /Users/<user>/Documents/the-handover-repo (project root) git worktree add .claude/worktrees/s15-lows-bundle origin/main -b feat/s15-lows-bundle # → created at .../the-handover-repo/.claude/worktrees/s15-lows-bundle ✅ # Later, investigation: cd /Users/<user>/Documents/the-handover-repo/.claude/worktrees/s15-lows-bundle && grep "_JINJA_ENV" src/ # → main shell cwd is now inside s15-lows-bundle worktree # Even later, second worktree: git worktree add .claude/worktrees/s15-csv-upload origin/main -b feat/s15-csv-upload # → created at .../s15-lows-bundle/.claude/worktrees/s15-csv-upload ❌ ``` **Fix applied in that session:** none mid-session (PRs all pushed correctly via branch refs). The lesson is to use absolute paths next time. ## Variant (v1.1): `../<name>` escapes UP to the repo parent, then a file-tool call "file does not exist" A second, even-easier-to-hit shape — cwd was correct (the project root), the relative path itself was wrong: ```bash cd /Users/x/Documents/myrepo # at project root — cwd is fine git worktree add -b docs/foo ../foo origin/main # ../ escapes to /Users/x/Documents/foo ❌ # intended .claude/worktrees/foo, but ../foo = the repo's PARENT dir tail ../foo/docs/file.md # works HERE — same bash, cwd still repo root # ... later, an Edit/Write/Read with the ASSUMED path: # Edit /Users/x/Documents/myrepo/.claude/worktrees/foo/docs/file.md # → "File does not exist" ← MISLEADING: the file exists at /Users/x/Documents/foo/... ``` The `../<name>` reads like "a worktree beside the others" but `.claude/worktrees/` is two levels **down**, so `../` goes the wrong direction entirely. The trap is the downstream symptom: the worktree was created fine and even `tail` in the *same* bash worked (cwd-relative), but the next file-tool call uses a hard-coded absolute path under `.claude/worktrees/` and reports the file missing — pointing you at the file, not at the worktree's real location. **Guards (in addition to the absolute-path rule above):** 1. To match the convention, pass the **full intended path**, not `../`: `git worktree add .claude/worktrees/<name> <ref>` (from the project root) or the absolute equivalent. 2. **Resolve the worktree's REAL path before constructing absolute paths for Read/Edit/Write:** ```bash git worktree list | grep <name> # the path column is the source of truth ``` Use that exact path — never an assumed `.claude/worktrees/<name>` — when the next tool call needs an absolute path. 3. If you see a file-tool "file does not exist" right after a `git worktree add`, suspect the worktree location before suspecting your file path. ## Variant (v1.2): drifted cwd makes `git diff -- <repo-relative-path>` return EMPTY while `--stat` shows the change Same root cause (main-shell cwd persisted into a subdir after an earlier `cd`), different — and very disorienting — symptom: **git pathspecs are resolved relative to cwd, but `--stat`/`--name-only` without a pathspec report against the repo root.** So the two disagree, and the file *looks* both changed and unchanged at once: ```bash # earlier in the session, cwd drifted into a subdir: cd /repo/<analytics_pkg>/cloudrun/<dashboard_app> && python -m pytest ... # persists cwd # later, trying to inspect a commit that DID touch bq_queries.py: git show --stat HEAD | grep bq_queries # → "bq_queries.py | 62 ++++--" (file IS in the commit) git diff HEAD~1 HEAD -- <analytics_pkg>/cloudrun/<dashboard_app>/bq_queries.py # → EMPTY ❌ (pathspec resolved from cwd = .../<dashboard_app>/<analytics_pkg>/... which doesn't exist) git show HEAD:<analytics_pkg>/.../bq_queries.py | grep <symbol> # → matches (object-name form is cwd-INDEPENDENT) ``` The tell is the **contradiction**: `git show --stat` / `git diff A B --stat` (no pathspec) lists the file as changed, but `git diff A B -- <repo-relative-path>` returns nothing, and `git show HEAD:<path>` (a `<rev>:<path>` *object name*, which is always repo-root-relative) shows the content fine. When a pathspec-filtered git command comes back empty but the unfiltered `--stat` disagrees, suspect cwd drift **before** suspecting the commit, the branch, a `.gitattributes` `-diff` driver, or a bad range. **Guards:** 1. `pwd` when a pathspec-filtered git result surprises you — the harness's own "Shell cwd was reset to …" tool-result lines are reporting the drift, not just noise. 2. Prefer a leading `cd /abs/repo/root && git diff … -- <path>` for any pathspec-filtered inspection, OR use the cwd-independent `git show <rev>:<path>` / `--stat` (no pathspec) forms. 3. A `cd` inside a **compound** Bash command (`cd X && cmd`) still mutates the persistent cwd for every later call — it is not scoped to that one command. ## Notes - Branch and push behavior are NOT affected because git tracks worktrees by `.git/worktrees/<name>` metadata, not by filesystem path. So this bug is silent — you only notice when a subagent reports a path mismatch or when you try to `git worktree remove` from the project root. - Variant v1.2's pathspec trap is not worktree-specific — it bites in any repo the moment cwd drifts into a subdir — but the fix is identical (absolute paths / repo-root `cd` / cwd-independent git forms), so it lives here with the other cwd-drift symptoms. - This is a property of the Claude Code Bash tool specifically. Other tools (e.g., Edit, Write, Read) don't have a persistent cwd — they take absolute paths or resolve against the project root. - The subagent variant (`subagent-bash-cd-wrong-worktree`) describes the opposite failure mode: subagent shells reset cwd per call, so a `cd` followed by `git commit` in a *separate* Bash call commits to the parent repo's HEAD. Both are real, and the fix is the same: use absolute paths and verify with `pwd` before any state-changing command. ## References - Claude Code Bash tool docstring: "The working directory persists between commands, but shell state does not." - Related skill: [subagent-bash-cd-wrong-worktree](../subagent-bash-cd-wrong-worktree/SKILL.md) — the inverse failure mode in subagent shells. - Related skill: [using-git-worktrees](../using-git-worktrees/) — general worktree workflow.