Use when landing (merging) a PR via SPR and cleaning up afterward. Triggers on "land PR", "merge PR", "spr land", "ship it", or when a PR is approved and ready to merge.
This skill uses a dev bookmark as the rebase target — it sits one
commit on top of master@origin. If dev doesn't exist in the repo,
use main@origin or master@origin as the rebase target instead.
Check with: jj bookmark list | grep '^dev'
Before running ANY SPR command, check if you're in a jj workspace.
if [ -f .jj/repo ]; then
MAIN_REPO=$(dirname "$(dirname "$(cat .jj/repo)")")
cd "$MAIN_REPO"
fi
SPR requires .git/ which only exists in the main colocated repo, not in
workspaces. All jj spr commands must run from the main repo.
Land a PR by squash-merging it on GitHub, then sync the local repo. Covers single PRs and stacked PR landing with proper cleanup.
Before landing:
PR is approved (if spr.requireApproval is configured)
gh pr view <number> --json reviewDecision
CI is passing
gh pr checks <number>
No merge conflicts -- SPR will refuse to land if the PR can't be cleanly squash-merged
jj spr land -r <change-id>
This:
Then sync locally:
jj git fetch
jj rebase -r @ -d dev
Critical to understand: jj spr land retargets the PR base from its
synthetic base branch to master BEFORE squash-merging. This is automatic.
You never need to manually retarget a PR base to master. See land.rs
lines 102-147.
This means a PR can safely "sit on" a synthetic base branch between
jj spr diff updates. The synthetic base is cosmetically ugly on GitHub
but functionally correct: the diff shows only that PR's own changes, and
landing works because SPR retargets at merge time.
Always land bottom-up. Landing from the middle or top of a stack is dangerous and will leave orphaned PRs.
jj spr land -r <bottom-change-id>
SPR retargets to master, squash-merges, and deletes the landed PR's head and base branches.
jj git fetch
# The landed change is now empty (its content is on the remote default branch)
jj abandon <landed-change-id>
# Rebase dev onto updated master@origin, then rebase stack onto dev
jj rebase -r dev -d master@origin
jj rebase -s <next-change> -d dev
Where <next-change> is the change that was directly above the landed one.
Use -s (not -r) to rebase the entire subtree.
jj spr diff -m "rebased after landing" -r <next-change>::<top-of-stack>
Always pass -m — these are existing PRs being updated, and without
-m SPR prompts interactively.
What this does and does NOT do:
The next PR in the stack will still show its synthetic base branch as the target on GitHub. This is expected and harmless. When you land that PR (Step 1 of the next cycle), SPR retargets to master automatically.
NEVER suggest gh pr edit <number> --base master as a workaround.
This bypasses SPR's synthetic commit management and can produce wrong diffs.
If a cosmetically clean master target is required, close the PR, strip the
Pull Request: URL from the commit message, and recreate with
jj spr diff -r <change>.
jj log # clean graph, no orphaned changes
jj spr list # remaining PRs look correct
Check GitHub: diffs should show only each PR's own changes. Ignore the synthetic base branch name in the PR target -- it's cosmetic.
If all PRs in a stack are approved, land them one at a time, bottom-up:
# Land first
jj spr land -r <change-1>
jj git fetch
jj abandon <change-1>
jj rebase -s <change-2> -d dev
jj spr diff -m "rebased after landing" -r <change-2>::<top>
# Land second
jj spr land -r <change-2>
jj git fetch
jj abandon <change-2>
jj rebase -s <change-3> -d dev
jj spr diff -m "rebased after landing" -r <change-3>::<top>
# Continue until done
Do not try to land all at once. Each land changes the trunk, and the next change needs to be rebased before it can be landed.
You can skip the jj spr diff step between landings if you're landing
the entire stack in one session. SPR retargets at land time, so the
intermediate diff updates are only needed if you want correct GitHub diffs
while waiting for reviews.
After all landing is complete:
# Sync and rebase working copy
jj git fetch
jj rebase -r @ -d dev
# Verify clean state
jj log
The landed changes will appear as immutable commits in the log.
Orphan SPR branches from landed PRs should be cleaned up automatically
(SPR deletes head + base branches on land). If any remain, use
jj spr cleanup to find and remove them.
Always use jj spr land. Never click "Merge" on GitHub or use
gh pr merge.
jj spr land retargets stacked PRs from their synthetic base to master
before merging. If you merge on GitHub directly:
PR targeting master (non-stacked): merge goes to master. Works, but
you skip SPR's branch cleanup and local sync. You'll need to manually
jj git fetch, abandon the change, rebase, and delete leftover remote
branches.
PR targeting a synthetic base (stacked): merge goes to the synthetic base branch, NOT master. Master is not updated. Your code ends up on a dead-end branch. Recovery requires cherry-picking to master or closing and redoing the PR.
If someone already merged a stacked PR on GitHub directly:
git log origin/master --oneline -5jj spr cleanup --confirmIf the stack is severely broken (multiple stale bases, ghost changes,
orphaned PRs), use the jj-spr-recovery skill or /spr-recover command
for a full diagnosis and recovery.
jj git fetch -- local repo won't know about the merge
on GitHub. Must fetch before rebase.-r instead of -s for rebase -- -r rebases only one
change, -s rebases the entire subtree. Use -s for stacks.gh pr edit --base master -- bypasses
SPR's synthetic commit system. Let SPR handle retargeting at land time.