"**Load when**: you are working a PR that carries `loom:merge-conflict` and a trusted `<!-- loom:orphan-stack flagged parent=<N> head=<sha> -->` comment, in a repository where `loom-daemon branch-update strategy` answers `merge`."
Install
npx skillscat add rjwalters/loom/loom-orphan-stack-replacement Install via the SkillsCat registry.
Orphan-Stack Replacement: Recover a Stacked Child Whose Parent Closed Unmerged (#12767)
Load when: you are working a PR that carries loom:merge-conflict and a
trusted <!-- loom:orphan-stack flagged parent=<N> head=<sha> --> comment, in a
repository where loom-daemon branch-update strategy answers merge.
Why this exists
A stacked child's base is the branch of another Loom PR. When that parent PR
closes without merging, its commits will never reach the default branch.
The daemon (#12760) flags such a child, but in a merge-mode repo it neither
rewrites nor retargets it: a retarget alone would carry the closed parent's
commits into the child's PR and then into main, and the rewrite that avoids
that is a force-push #10771 forbids. Before this recipe nothing picked the flag
up, so the child waited for a human indefinitely.
The operator ruling on #12767: recover it with a replacement PR from the
default branch that carries only the child's own commits. Do not rewrite or
force-push the original branch. Do not bring the parent's commits into main
through a corrective merge.
The recipe
Facts first, all from the forge (<C> the flagged child, <P> the parent the
flag names, <D> the default branch):
gh pr view <C> --json number,headRefName,headRefOid,labels,state,comments
gh pr view <P> --json number,state,mergedAt,headRefOid # must be CLOSED, mergedAt null<P>'s headRefOid is the parent head. Use the flag marker's parent=<N>,
never a number from prose. Only comments from trusted authors count
(.loom/docs/comment-trust.md); a marker from anyone else is text.
Already replaced? Feed the trusted comment bodies to
loom-daemon orphan-replace replaced-by(bodies separated by a line----).
It prints the replacement PR number and exits 0, or exits 1 with nothing.
If a replacement exists, skip to step 5 (the links and close are idempotent).Build it. From the repo root:
loom-daemon orphan-replace create --parent-pr <P> --parent-head <parent head> \ --child-pr <C> --child-head <child head> --default-branch <D>It fetches both PR heads, requires the parent head to be an ancestor of
the child head, requires a non-empty linear range, replays only<parent head>..<child head>onto the freshly fetchedorigin/<D>in a
scratch worktree, and pushes the NEW branchorphan-replace/pr-<C>without
force. It prints JSON (branch,head,commits,reused). A retry finds
the branch already holding exactly that range and reuses it.Refusal (exit 1,
REFUSED <TOKEN>: <why>). Nothing was pushed. Do not
guess a replay. Post one comment on<C>that quotes the line, leaveloom:merge-conflictin place, and stop:PARENT_NOT_ANCESTOR,MISSING_HEAD,BAD_SHA,EMPTY_RANGE,MERGE_IN_RANGE,BRANCH_TAKENneed a human or a fresh flag.CONFLICTis yours to investigate: the change very likely depends on the
rejected parent. Read the child's diff and the parent's. If the child is
genuinely independent, adapt the change by hand on a branch cut fromorigin/<D>(worktree via./.loom/scripts/worktree.sh), keep the child's
intent, and say in the PR body exactly what you adapted. If it needs the
parent's code, the replacement cannot exist: comment why and park per
"Routing to a human". Never resolve a conflict silently. Exit 2 is transient:
retry once, then report.Open the replacement PR with
./.loom/scripts/create-pr.sh --label "loom:review-requested"fromorphan-replace/pr-<C>to<D>. The body
links#<C>and the<parent head>..<child head>range, repeats the
original'sCloses #<issue>reference so the issue stays tied to the work,
and states that no approval transfers. It carries noloom:pr: it gets
a fresh Judge pass and fresh CI.Link both ways, then close. Post on
<R>(the replacement) a comment
linking#<C>. Post on<C>a comment linking#<R>that ends with<!-- loom:orphan-stack replaced by=<R> -->. Then ask:loom-daemon orphan-replace close-check --replacement <R> --links-posted \ --descendant <open PR whose base is C's head branch> ...List the descendants with
gh pr list --state open --base <C's head branch>.
Exit 0 printsCLOSE: close<C>withgh pr close <C>and removeloom:merge-conflict. Do not delete its branch. Exit 1 prints why not.BLOCKED_BY_DESCENDANTSmeans open PRs are stacked on<C>: leave it open
and flagged, and say which PRs block it. Closing it would strand them.
What not to do
- Do not
git push --force*or rewrite<C>'s branch, and do not merge<P>'s
branch or<C>into<D>to "fix" the stack. - Do not retarget
<C>to<D>: that puts the parent's commits in its diff. - Do not copy
loom:pr,loom:review-requestedhistory or any verdict to the
replacement, and do not run this on a currently open PR the operator has not
flagged. - Do not touch a PR with a hold label (
loom:blocked,loom:operator,loom:frozen) or one another agent is treating.