jj-stack merge asks GitHub to merge a group of pull requests starting at the bottom of your
stack. Once the PRs have merged, jj-stack sync updates your local stack and any remaining
pull requests.
GitHub can perform the merge immediately or put it through a merge queue. An immediate merge
is called a direct merge. In both cases, jj-stack merge waits for completion and runs
the sync automatically.
Before merging
If you rewrote one of your changes after submitting it, submit your stack again, even if that change’s diff is unchanged:
jj-stack submit <head-change-id>
jj-stack merge <head-change-id>
jj-stack merge checks that the changes to merge still match the commits you last submitted and
that their PR branches and pull requests have not moved unexpectedly. GitHub decides whether
checks, approvals, conflicts, and repo rules allow the merge.
jj-stack list and jj-stack view show review decisions, checks, and GitHub’s merge-state
warnings separately. review required means GitHub still requires a review. A PR can be
approved with passing checks and still show merge blocked, for example when review
conversations remain unresolved.
To inspect unresolved review threads and failed or pending checks, including links, run:
jj-stack view --verbose <head-change-id>
Verbose output includes thread excerpts, even for outdated threads. If GitHub reports a blocker without exposing a specific reason, the output says so and links to the PR. Checks are the results GitHub has received; a required check that has not reported yet is absent. Even without a warning, approval and passing checks do not guarantee that GitHub will accept a merge.
Choose a merge method
For a direct merge, merge uses your repo’s only allowed merge method if there is just one.
With several allowed methods and an unsigned stack, it prefers rebase, then squash, then a merge
commit. Choose an allowed method with --method, or set a default once:
jj config set --repo jj-stack.merge_method squash
When several methods are allowed and your stack contains signed commits, choose a method with
--method or jj-stack.merge_method because merging can discard commit signatures. This also
covers changes you aren’t merging yet: GitHub may rewrite them when earlier changes are merged.
Rebase and squash replace the original commits and their signatures. GitHub may sign a squash result with its own key. A merge commit preserves the commits being merged. Choosing a method does not guarantee that all signatures in the stack survive.
A merge queue chooses its own method, including for signed stacks; --method is ignored.
Choose how much of your stack to merge
By default, merge selects consecutive open, non-draft PRs from the bottom of the stack, each
still matching what you submitted. It sends one request for that group. GitHub decides whether
reviews, checks, conflicts, and repo rules allow the group to merge.
If a check or approval blocks that request, jj-stack reports the rejection. It does not retry
smaller groups automatically. To merge fewer PRs, select the last PR you want to land with
--pull-request.
For example, in A → B → C, suppose the PRs are #1, #2, and #3. To merge only A:
jj-stack merge --pull-request 1 --method squash
Scroll through the full transcript
# A, B, and C are submitted as PRs #1, #2, and #3
# A is ready to merge; keep B and C open
❯ jj-stack merge --pull-request 1 --method squash
Using PR #1 for change solxnvlm
Waiting for GitHub to merge. Ctrl-C stops waiting without cancelling.
Trunk: main, observed at "base"
Merge completed:
✓ GitHub merge request: merged PR #1 into main through change solxnvlm (commit ID 3ee70f78)
GitHub reported final trunk commit 7d11720f.
Updating the local stack after the completed merge:
Removing merged changes from the bottom of the stack: solxnvlm
Submitted changes:
○ yoquuonk C: add UI: already pushed, PR #3 unchanged
│
○ mrtszvus B: add API: already pushed, PR #2 unchanged
│
◆ lqtyzmym A: refactor shared model (#1)
│
Top of stack: PR #3
Cleanup:
✓ remote branch: delete jj-stack/a-refactor-shared-model-solxnvlm@origin
✓ forget the saved link between PR #1 and solxnvlm
# The direct merge updated the local stack and the same remaining PRs
❯ jj-stack view yoquuonk
Submitted stack (PR #3):
○ yoquuonk C: add UI: PR #3
│
○ mrtszvus B: add API: PR #2
│
◆ lqtyzmym A: refactor shared model (#1)
After the command finishes, PR #1 is merged, PR #2 targets main, and PR #3 still targets PR #2’s
branch. Both remaining PRs keep their numbers and discussions:
flowchart BT M["main<br/>includes A"] --> B["B: API<br/>same PR #2"] --> C["C: UI<br/>same PR #3"]
Selecting PR #2 instead asks GitHub to merge A and B together, leaving C open.
Using --pull-request lets jj-stack merge through the named PR while still updating the
changes above it. With --pull-request 1, it merges A and then updates B and C. Passing A’s
change ID instead would leave B and C out of the selected stack, so the command would refuse to
proceed because all three PRs belong to the same GitHub stack.
Finish after GitHub merges
After GitHub merges some or all of your pull requests, sync fetches the updated trunk and
rebases your remaining changes onto it. It removes any obsolete local copies of the merged
changes, updates the remaining PRs, and deletes PR branches that are no longer needed. If your
working copy is on a merged change, sync first moves it to a new empty change on trunk.
Select the stack by its head change ID or by any linked pull request:
jj-stack sync <head-change-id>
jj-stack sync --pull-request <pr>
sync --pull-request updates the complete local stack containing the named PR, including
changes above it. The selected PR can already be merged.
If someone pushes a PR’s submitted commit straight to trunk instead of merging the PR, sync
closes that PR and cleans up, provided the PR is not part of a GitHub stack.
If no PR has merged, no submitted commit has reached trunk, and GitHub has not rebased the stack,
sync leaves the pull requests unchanged. Run jj-stack submit explicitly when you want to
publish local changes.
After a squash merge, trunk contains a new commit for the merged work, but your remaining
changes may still depend on the original local changes. sync rebases that work onto the
squashed result and removes the old copies. Other merge methods also need sync to update
the remaining PRs and remove unused branches.
Merge queues
GitHub tests each queued PR on a temporary merge commit that combines it with the PRs ahead of
it, so those checks appear on that commit rather than on the PR’s own Checks tab. While waiting,
merge shows each PR’s queue position and the state of those checks.
If GitHub removes a PR from the queue, merge stops, reports GitHub’s reason, links to the
checks on that commit, and names the next step. See queue removal recovery.
While any selected pull request is queued, jj-stack submit refuses to update the stack and
jj-stack sync leaves it unchanged; rerun jj-stack merge to resume waiting.
Leaving before GitHub finishes
Use --no-wait to return as soon as GitHub accepts the request, or press Ctrl-C to stop
waiting. Neither cancels the request. Rerun the same jj-stack merge to keep watching it, or run
jj-stack sync <head-change-id> once GitHub finishes.
Merges outside jj-stack
If you or someone else merged your stack through the GitHub UI, gh, or another client, run the
same sync command after GitHub reports that the merge finished.
Rebasing from GitHub
After GitHub’s Rebase stack action finishes, run jj-stack sync <head-change-id> to bring
that rebase into your local stack.
GitHub’s rewritten commits do not retain jj change IDs. jj-stack sync checks that the PR order
and contents match, rebases your original changes, and updates the PR branches with commits that
retain their change IDs. It stops if local edits or different contents on GitHub prevent a match.
Several merged stacks
To sync every local stack affected by a completed merge, run:
jj-stack sync --all
This also cleans up merged PRs whose local changes are gone. If one stack cannot be updated, jj-stack explains why and continues with independent stacks.
sync --all handles completed merges. To follow up on GitHub’s Rebase stack action,
name the stack with sync <head-change-id>.
If merge fails after GitHub merges your pull requests
A failed local update or network interruption can leave work unfinished after the PRs merge. Follow the hint in the error. If the local rebase produced conflicts, follow sync conflict recovery.
If the local update has not finished, inspect the stack and rerun sync:
jj-stack view <head-change-id>
jj-stack sync <head-change-id>
If only cleanup failed, run the jj-stack cleanup --pull-request <pr> commands in the hint.
The hint names each PR because the merged local changes may already be gone.
Your pull requests are already merged, so do not retry jj-stack merge.
When trunk moves without one of your pull requests merging
jj-stack sync handles completed GitHub merges and stack rebases. If trunk merely advanced,
fetch it, rebase your changes with jj if needed, then submit the rewritten changes:
jj git fetch
jj rebase -b '<change-id>' -o 'trunk()'
jj-stack submit <head-change-id>
Use any change in the stack with -b; jj finds its base and moves the whole stack, including
forks. You can even omit -b to use jj’s default of @.
Rebase when your work needs the latest trunk or GitHub requires it before merging.