[BUG] Agent isolation: "worktree" bases the worktree on origin/HEAD, which can name an abandoned branch with an unrelated tree

Status Closed — duplicate
Reported on v2.1.227
Maintainer reply None cached
Activity 1 comment · opened Aug 11, 2026 · closed Aug 15, 2026

Preflight Checklist

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

What's Wrong?

A subagent spawned with isolation: "worktree" got a worktree based on origin/HEAD instead of the session's current HEAD. This is the same base-ref selection bug reported in #62630, #54940, #39506 and #48095 — but this report adds a failure mode the earlier ones didn't cover, and which makes the impact qualitatively worse than "unpushed work is invisible".

In a repo that has migrated its default branch, origin/HEAD can point at a branch holding an entirely different application.

Our repo went GitFlow: develop is the integration branch, and main is deprecated — deliberately frozen at a pre-cutover commit and documented as "do not push to it, do not PR into it, do not merge from it." Nobody re-pointed origin/HEAD when that happened, and nothing in the normal workflow ever reads it. So:

$ git symbolic-ref refs/remotes/origin/HEAD
refs/remotes/origin/main

$ git log --oneline -1 origin/main
284a0f9 Add normalized Base Camp 15W 5-Door and Z-Door locker images (all 13 colors, Solid + Perforated)

That commit is twelve commits deep in the pre-cutover history. Compare the two trees — this is not a stale version of the same project, it is a different project layout:

$ git ls-tree --name-only 284a0f9        # what the agent's worktree got (origin/HEAD)
.gitignore
manage.py
myproject
requirements.txt
sheets
static
templates

$ git ls-tree --name-only a4db64a        # what the session was actually on (HEAD)
.claude
.github
.planning
CLAUDE.md
Dockerfile
apps
config
docs
manage.py
pyproject.toml
scripts
tests

No .planning/. No apps/. No pyproject.toml — instead a requirements.txt that the current repo's pre-commit hooks actively reject. The agent was dispatched to execute a phase plan against a checkout where its entire project did not exist.

What Should Happen?

The worktree should be created from the session's current HEAD, per the documented behavior. If a fallback to a remote ref is ever used, it should be surfaced explicitly at the creation boundary rather than applied silently — e.g. Note: basing worktree on origin/main (HEAD was <reason>).

origin/HEAD is a poor fallback in particular: it is a piece of local remote-tracking metadata set by git clone (or git remote set-head) that no ordinary workflow reads or maintains. In any repo that has renamed or migrated its default branch, it silently rots, and nothing surfaces that until something like this consumes it.

Error Messages/Logs

Two signals from the incident. The first is the agent's own environment banner, which reported its worktree path as not being a git repository at all:

Is directory a git repo: No

That is worth flagging on its own — #68214 documented isolation: "worktree" failing outright behind the same misdetection and was closed not_planned. If that check is what feeds base-ref resolution, a false answer there would explain falling back to origin/HEAD on every spawn rather than intermittently.

The second is the guard that caught it. Our dispatch template asserts the worktree base before any write:

EXPECTED_BASE=<parent HEAD captured at dispatch>
ACTUAL_BASE=$(git rev-parse HEAD)
[ "$ACTUAL_BASE" = "$EXPECTED_BASE" ] || exit 42

It exited 42. Nothing was written. Without it, the agent would have found no .planning/ directory and — being helpful — likely recreated the scaffolding it expected, on top of dead history, on a branch nobody watches.

Steps to Reproduce

The precondition is a repo whose origin/HEAD is not the branch you work from. That is normal in any project that migrated its default branch and left the old one in place.

  1. Clone a repo whose remote default was later migrated, so origin/HEAD still points at the old branch:

``
git symbolic-ref refs/remotes/origin/HEAD # -> refs/remotes/origin/main
git log --oneline -1 origin/main # old, frozen tip
git log --oneline -1 origin/develop # the branch actually in use
`
(To construct one:
git remote set-head origin main while doing all real work on develop`.)

  1. Check out a feature branch off the real integration branch: git checkout -b feature/x origin/develop
  2. Note the current HEAD: git rev-parse HEAD
  3. Spawn a subagent with isolation: "worktree".
  4. Have the subagent run git rev-parse HEAD and ls as its first commands.

Expected: the SHA from step 3, and the working tree you are on.

Observed: the tip of origin/main, and that branch's tree.

Scope / honesty note

I verified the repo-state preconditions and the two trees above directly, on the machine and repo where this happened; the wrong-lineage spawn itself is from our session record of 2026-08-10 rather than a live re-run, and the base it landed on (284a0f9) is exactly origin/HEAD. I have not isolated whether the trigger is the git-repo misdetection (#68214), the concurrent-spawn race described in #62630, or plain default-ref fallback.

Why file this when the cluster exists

The EnterWorktree variants (#39506, #54940) were closed completed. The isolation: "worktree" variants (#62630, #68214) were closed not_planned, and there is currently no open issue tracking this. It still reproduces on 2.1.227.

The earlier reports argued impact in terms of losing unpushed commits or a version downgrade — real, but recoverable, and both assume the wrong base is at least the same project. This report is the case where it isn't: origin/HEAD can name a branch that a repo has explicitly abandoned, and then an autonomous agent with write access is pointed at it. The blast radius is bounded here only because we assert the base at dispatch, which is a workaround every consumer has to invent independently.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗