You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When /loom:doctor (or a loom-doctor subagent) runs on an external-fork PR — i.e., a PR whose branch name doesn't include a Loom issue number — it has no obvious worktree path to use, so it falls through to gh pr checkout <N> in whatever directory the agent was invoked from. In practice that's the orchestrator's main worktree, which means:
The orchestrator's HEAD switches from main to the PR's branch
Any agent that subsequently tries to run from the same shell sees the wrong git state
I had /loom:doctor take over jperla's external PR #3348 (jperla/loom:fix/claude-code-2.1-compat). The doctor ran gh pr checkout 3348 in the orchestrator's main repo worktree. After the doctor finished, the orchestrator was on branch fix/claude-code-2.1-compat instead of main, and src-tauri/gen/ was sitting untracked in the working tree (left over from a state when src-tauri/ existed before #3353 removed it).
Recovery was manual: git checkout main && git pull --ff-only + a rm -rf src-tauri/ cleanup. If the orchestrator had committed work in the wrong branch state between the doctor's exit and the manual recovery, that would have ended up on jperla's branch by accident.
For internal-Loom PRs this isn't a problem because the doctor uses .loom/worktrees/issue-N/ derived from the issue number in the branch name (feature/issue-N). External PRs have no such marker.
Proposal
When the doctor (or any subagent that needs to mutate a PR branch) operates on an external-fork PR:
Create a dedicated worktree at .loom/worktrees/pr-<N>/ (a different path than issue worktrees so the pr- prefix is unambiguous).
cd into that worktree before any git/gh pr checkout mutation.
Push to the external fork's branch from inside the dedicated worktree.
The worktree gets cleaned up at the end of the doctor pass (and on PR merge by merge-pr.sh).
The branch-name heuristic to distinguish issue PRs from external-fork PRs:
Branch matches feature/issue-<N> → use existing issue-worktree path .loom/worktrees/issue-<N>/
The merge-pr.sh script already prints a warning when it can't derive an issue number from the branch:
Could not determine issue number from branch 'fix/claude-code-2.1-compat' for worktree cleanup
That warning would become a positive — merge-pr.sh and the doctor would cooperate on the pr-<N> path.
Acceptance
loom-doctor subagent / defaults/.claude/commands/loom/doctor.md updated to:
Detect whether the PR's branch matches feature/issue-<N> or is external/ad-hoc
Create .loom/worktrees/pr-<PR_NUMBER>/ (with .loom-managed sentinel) when the branch doesn't fit the issue pattern
cd into that worktree before any mutation
merge-pr.sh extended to clean up .loom/worktrees/pr-<N>/ on merge (in addition to existing .loom/worktrees/issue-<N>/ cleanup)
Worktree-creation helper (.loom/scripts/worktree.sh) accepts an explicit pr-<N> mode or is bypassed in favor of git worktree add directly (decision in scope of implementation)
Regression test: simulate a doctor pass on a PR with branch fix/foo-bar; verify the orchestrator's CWD HEAD is unchanged after the doctor returns
Out of scope
Auto-detecting that a PR is external vs internal by headRepositoryOwner rather than branch name. The branch-name heuristic is sufficient — internal PRs always use feature/issue-N, and external PRs can be anything.
Backfilling pr- worktree paths for existing in-flight PRs. Apply only to new doctor passes.
Problem
When
/loom:doctor(or aloom-doctorsubagent) runs on an external-fork PR — i.e., a PR whose branch name doesn't include a Loom issue number — it has no obvious worktree path to use, so it falls through togh pr checkout <N>in whatever directory the agent was invoked from. In practice that's the orchestrator's main worktree, which means:HEADswitches frommainto the PR's branchmain(in the case I just hit, an orphanedsrc-tauri/gen/directory left over from a pre-feat!: remove Tauri desktop app surface — CLI + daemon only #3353 branch)Concrete incident (2026-05-28)
I had
/loom:doctortake over jperla's external PR #3348 (jperla/loom:fix/claude-code-2.1-compat). The doctor rangh pr checkout 3348in the orchestrator's main repo worktree. After the doctor finished, the orchestrator was on branchfix/claude-code-2.1-compatinstead ofmain, andsrc-tauri/gen/was sitting untracked in the working tree (left over from a state when src-tauri/ existed before #3353 removed it).Recovery was manual:
git checkout main && git pull --ff-only+ arm -rf src-tauri/cleanup. If the orchestrator had committed work in the wrong branch state between the doctor's exit and the manual recovery, that would have ended up on jperla's branch by accident.For internal-Loom PRs this isn't a problem because the doctor uses
.loom/worktrees/issue-N/derived from the issue number in the branch name (feature/issue-N). External PRs have no such marker.Proposal
When the doctor (or any subagent that needs to mutate a PR branch) operates on an external-fork PR:
.loom/worktrees/pr-<N>/(a different path than issue worktrees so thepr-prefix is unambiguous).cdinto that worktree before anygit/gh pr checkoutmutation.merge-pr.sh).The branch-name heuristic to distinguish issue PRs from external-fork PRs:
feature/issue-<N>→ use existing issue-worktree path.loom/worktrees/issue-<N>/.loom/worktrees/pr-<PR_NUMBER>/The
merge-pr.shscript already prints a warning when it can't derive an issue number from the branch:That warning would become a positive —
merge-pr.shand the doctor would cooperate on thepr-<N>path.Acceptance
loom-doctorsubagent /defaults/.claude/commands/loom/doctor.mdupdated to:feature/issue-<N>or is external/ad-hoc.loom/worktrees/pr-<PR_NUMBER>/(with.loom-managedsentinel) when the branch doesn't fit the issue patterncdinto that worktree before any mutationmerge-pr.shextended to clean up.loom/worktrees/pr-<N>/on merge (in addition to existing.loom/worktrees/issue-<N>/cleanup).loom/scripts/worktree.sh) accepts an explicitpr-<N>mode or is bypassed in favor ofgit worktree adddirectly (decision in scope of implementation)fix/foo-bar; verify the orchestrator's CWDHEADis unchanged after the doctor returnsOut of scope
headRepositoryOwnerrather than branch name. The branch-name heuristic is sufficient — internal PRs always usefeature/issue-N, and external PRs can be anything.pr-worktree paths for existing in-flight PRs. Apply only to new doctor passes.Related
merge-pr.shworktree-cleanup path:.loom/scripts/merge-pr.shdefaults/.claude/commands/loom/doctor.md