worktree-outer-ls-mistaken-for-main-state · v1.0.0 · 2026-05-13 · sha256 5042f2577c44da6b

worktree-outer-ls-mistaken-for-main-state v1.0.0A

Immutable. This exact content is served forever at /api/v1/blob/5042f2577c44da6b.

---
name: worktree-outer-ls-mistaken-for-main-state
description: |
  Prevent citing files as "on main" when they actually live only in a sibling
  worktree's working tree. Use when (1) you're working inside a worktree at
  `<repo>/.claude/worktrees/<X>/` and need to verify whether a file or feature
  is on `origin/main`, (2) you're tempted to run `ls /path/to/outer-repo/...`
  or `find /path/to/outer-repo` to check, (3) you're about to cite that
  filesystem-walk result in a PR description, handoff doc, or commit message
  as if it represented main's state. Trap: in a multi-worktree setup, the
  outer repo's working tree is on whatever branch the user (or another
  session) last checked out — which can be ANY sibling worktree's feature
  branch, never guaranteed to be main. Running `ls` on the outer dir lists
  THAT branch's files, not main's. A handoff doc that says "X is on main" or
  "X was written by session S" based on outer-`ls` evidence will fail a
  reviewer's dead-reference check and ship a fabricated claim.
author: Claude Code
version: 1.0.0
date: 2026-05-12
---

# Worktree: Outer-`ls` Mistaken for Main-State

## Problem

You're working inside a git worktree at `<repo>/.claude/worktrees/<branch>/`.
You need to verify whether some file (e.g., `docs/handoffs/session_184_X.md`)
exists on `origin/main` — perhaps because:

- You're writing a handoff doc and want to cite "the canonical S184 prompt that's already on main"
- You're filing a PR body and want to confirm the file you're referencing actually exists
- You're cross-checking a claim from MEMORY.md against current main

The natural-feeling check is:

```bash
ls /Users/.../repo/docs/handoffs/ | grep session_184
# → session_184_drivers_breakdown_full_sweep_prompt.md
```

You walk up out of the worktree, list the outer repo's `docs/handoffs/`, see
the file, and cite it as "on main". **That conclusion is wrong** in any repo
with multiple worktrees in use.

## Context / Trigger Conditions

All four must be true to fire this trap:

1. The repo has **2+ active worktrees** (`git worktree list` shows >1 entry)
2. You're cwd-anchored inside one worktree (e.g., `.claude/worktrees/<X>/`)
3. You need to verify a claim about `origin/main`'s state
4. You reach for a **filesystem traversal command** (`ls`, `find`, `cat`, `tree`)
   on the outer repo's path instead of a git plumbing command

The outer working tree's `HEAD` can be on:
- The literal `main` branch (only if no one explicitly switched it)
- A sibling worktree's feature branch (if someone ran `git checkout <feature>` in the outer dir)
- A stale committed-but-not-pushed branch from days ago
- The branch of whichever worktree was created most recently — git sometimes resets the outer
  HEAD as a side effect of `git worktree add`

You cannot tell which from a casual `ls`. Concrete failure mode observed in S184b
(2026-05-12, PR #778): handoff doc cited `session_184_drivers_breakdown_full_sweep_prompt.md`
as "on main, unconsumed" based on outer-`ls`. The reviewer ran `git ls-tree origin/main`
and proved the file did not exist on main — it lived only in a sibling worktree's working
tree. Three textual mentions had to be rewritten before the PR could merge.

## Solution

### Step 1: Stop reaching for `ls` on the outer repo path

When the question is "does X exist on main?", `ls` is the wrong tool. The outer working
tree is just another working tree — its state is no more canonical than yours.

### Step 2: Use git plumbing against the explicit ref

```bash
# Does docs/handoffs/session_184_X.md exist on origin/main?
git ls-tree --name-only origin/main docs/handoffs/ | grep session_184

# What's the content of that file on main?
git show origin/main:docs/handoffs/session_184_X.md

# All files in a directory on main:
git ls-tree --name-only origin/main docs/handoffs/

# Listing across all refs (when you don't care WHICH branch, just whether it exists anywhere):
git log --all --oneline -- docs/handoffs/session_184_X.md
```

Always `git fetch origin main --quiet` before this audit so the local `origin/main` ref is
current — otherwise you're checking against a stale snapshot.

### Step 3: Don't conflate "exists somewhere" with "on main"

`git log --all` is great for "is this file anywhere git has ever seen?" but a hit doesn't
mean main has it. If the answer to "where does it live?" matters, follow up with:

```bash
git branch -a --contains $(git log --all --format='%H' -- docs/handoffs/session_184_X.md | head -1)
# Lists every ref that has the commit that introduced the file.
```

If `origin/main` is not in that list, the file is NOT on main — even if your outer-repo `ls`
showed it.

### Step 4: Pre-PR sanity check on any handoff/PR citing files "on main"

Before pushing a handoff doc or PR body that asserts "<file> is on main" / "<file> was written
by session S" / "<file> exists in repo":

```bash
# Extract every cited path
grep -oE 'docs/[a-zA-Z0-9_/-]+\.(md|py|sqlx|html|json)' your_doc.md \
  | sort -u \
  | while read path; do
      git ls-tree --name-only origin/main "$path" >/dev/null 2>&1 \
        && echo "✓ on main: $path" \
        || echo "✗ MISSING on main: $path"
    done
```

Anything `✗ MISSING` either needs a real annotation ("this file lives on a non-main branch
in worktree Y; recover with `git show <branch>:<path>`") or removal.

## Verification

```bash
# Before fixing
ls /path/to/outer-repo/docs/handoffs/ | grep session_184
# Shows session_184_drivers_breakdown_full_sweep_prompt.md

# Truth
git ls-tree --name-only origin/main docs/handoffs/ | grep session_184
# Returns nothing — file is NOT on main

# Where it actually lives
git log --all --oneline -- 'docs/handoffs/session_184_*' 2>/dev/null | head -3
# If no commits printed: file is in some worktree's working tree but never committed.
# Otherwise: lists the branch(es) that committed it.
```

## Example

In S184b (2026-05-12), while writing `session_184b_summer_wow_pre_launch_zeros_handoff.md`,
I needed to characterize the relationship to the canonical S184 session. I ran:

```bash
ls /Users/<user>/Documents/the-project-repo/docs/handoffs/ | grep 184
# → session_184_drivers_breakdown_full_sweep_prompt.md
```

…and wrote in the handoff: "canonical S184 drivers-breakdown sweep prompt at
`session_184_drivers_breakdown_full_sweep_prompt.md` on main, unconsumed."

PR #778 code-review caught it:

> Sibling reference is broken: `docs/handoffs/session_184_drivers_breakdown_full_sweep_prompt.md`
> exists neither on main nor in the worktree. The handoff cites it 3 times as "on main,
> unconsumed" / "written by S183" — that file does NOT exist anywhere reachable.

Verification with `git ls-tree`:

```bash
git ls-tree --name-only origin/main docs/handoffs/ | grep -i "184\|drivers" | head
# session_107a_topdrivers_handoff.md
# session_110_topdrivers_triple_fix_handoff.md
# (no session_184_*)
```

The file existed only in a sibling worktree's working tree (the outer-repo path's `HEAD`
had been switched to that sibling's branch at some point). Fix: rewrite the 3 mentions to
describe S184 as "an unrelated parallel drivers-breakdown stream running in a sibling
worktree, no artifact on main yet" — drop the specific filename, drop the "on main" claim.

Recovery commit: `fc4d523f` on `docs/s184b-summer-wow-handoff`; merged via PR #778.

## Notes

- **The skill applies to subagents too.** If you dispatch a subagent and ask "find files on
  main matching X", the subagent will instinctively `ls` the outer repo unless you brief it
  to use `git ls-tree origin/main`. Spell it out in the agent prompt.
- **`gh ls-tree` doesn't exist** — but `gh api repos/<owner>/<repo>/contents/<path>?ref=main`
  works as a remote-only fallback when you can't trust the local `origin/main` ref freshness.
  Heavier than `git ls-tree`; only use when offline-from-local-git.
- **CI catches some but not all instances.** GitHub Actions running from `actions/checkout@v4`
  fetches `merge_commit_sha`, not main — file-existence asserts in CI that use plain `[ -f ]`
  in the workflow may not catch this class. Verification belongs in handoff-doc review (per
  `session-handoff` v1.6 Phase 4 step 22 dead-reference check), not in CI.
- **Why git's `worktree add` can switch outer HEAD**: when you `git worktree add ../other
  some-branch`, the outer working tree keeps its current branch UNLESS the user ran a
  `git checkout` in the outer dir at some point. The instability comes from human action,
  not git itself — but the human action is invisible to a session that joined later.

## See Also

- `subagent-bash-cd-wrong-worktree` v1.0.0 — sister: `cd` doesn't persist between Bash calls in
  subagents; commits land on the outer working tree's branch instead of the worktree's branch.
  Different mechanism (cd state) but same root cause (multi-worktree disorientation).
- `session-handoff-number-collision-with-unmerged-sibling` v1.1.0 — picking the wrong session
  NUMBER because a sibling's same-numbered handoff isn't on main yet. Same family (parallel-
  worktree state confusion); different trigger (which-N-do-I-use vs does-file-X-exist).
- `worktree-historical-test-replay-missing-dirs` — replaying historical tests in a worktree
  misses dirs that are only on newer commits. Same "outer-vs-current" frame inversion.
- `flask-debug-cross-worktree-edit-stale` — Flask reloader picks up edits from another worktree
  via shared module imports. Sibling problem at the runtime layer.
- `using-git-worktrees` — broader worktree workflow guidance, doesn't focus on the
  filesystem-walk trap.