GitHub Stacked Pull Requests (gh stack)
Stacked pull requests split a large change into a chain of small, dependent pull requests. Each branch ("layer") builds on the one below it; each PR's base is the branch beneath, so reviewers see only that layer's diff. GitHub links the PRs into a first-class stack object with a stack map in the merge box.
Status: public preview — behaviour is subject to change. Requires GitHub CLI
(gh) 2.90.0+, Git 2.20+, and stacked PRs enabled for the repository (exit
code 9 means they are not).
Hard constraints
- All branches must live in the same repository — cross-fork stacks are not supported.
- A stack must have a linear history (no merge commits, no diverged branches) before it can merge.
- A PR in a stack merges only when it and every PR below it meet all merge requirements.
- Merged, merging, and queued PRs can never be removed from a stack.
- Merge requirements cannot be bypassed when merging stacked PRs.
Installation
gh extension install github/gh-stack
gh stack alias # optional: installs `gs` wrapper in ~/.local/bin/
Uses existing gh authentication (gh auth login if needed).
Delivery contract
Apply lifecycle actions only within the user's authorized scope. A request to inspect or rebase a stack does not itself authorize review requests or merges. For an authorized convergence task:
- Treat GitHub as the durable delivery record. Preserve unpublished local work, but do not count a commit, gate report or disposable-remote push as delivered. Verify the actual remote head after publication.
- Keep one authoritative candidate per PR: repository and Git common directory, branch/worktree, parent PR and boundary, local/remote SHAs, gate/review state, delivery owner, next external action and last PR transition time. Label alternative candidates as preserved evidence, not competing delivery heads.
- Prioritize the merge frontier: the lowest unmerged layer. Once its parent lands, assign its final synchronization, gates and publication before optional upper-layer refinements. Continue independent work where dependencies permit.
- Every implementation handoff names who commits and pushes. Local-only work needs a named publication owner; a delivery task ends with verified remote parity and the next authorized hosted stage, or a concrete blocker.
- Publish a validated candidate promptly. Do not wait for old-head CI to turn green before pushing the fix, or for unrelated upper layers to finish. Never bypass required candidate-bound gates to satisfy a progress deadline.
- During long-running convergence, after 30 minutes without a PR transition, check for an actionable push, ready transition, review request or merge. Otherwise name the frontier blocker and its owner. Active CI/review is valid waiting; repeated inventories, local reports and issue updates are not PR delivery. Change approach if the same blocker recurs without new evidence.
Before publishing only part of a damaged or partially validated stack, read partial-stack delivery. It also defines the delivery handoff and publication receipt.
Routing guide
| Task | Approach |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| Start a new stack | gh stack init <branch> (see workflow below) |
| Add a layer on top | gh stack add <branch> from the topmost branch |
| Open/update PRs on GitHub | gh stack submit |
| Daily catch-up (fetch, rebase, push, prune) | Establish replay evidence before gh stack sync --prune |
| Fix something in a lower layer | See "Editing a lower layer" |
| Trunk moved / history not linear | gh stack rebase, then gh stack push |
| Reorder, rename, fold, drop, insert branches | gh stack modify (interactive TUI) |
| Merge some or all of the stack | gh stack merge |
| Link pre-existing PRs/branches into a stack | gh stack link (no local tracking) |
| Move between layers | gh stack up, gh stack down, gh stack top, gh stack bottom, gh stack trunk, gh stack switch |
| Check out someone else's stack | gh stack checkout <stack-or-pr-number> |
| Dissolve a stack | gh stack unstack (--local to keep it on GitHub) |
| Full flags, exit codes, env vars | references/cli-reference.md |
Core workflow
# 1. Start the stack: creates and checks out the first branch off the trunk
gh stack init auth-layer # --base develop to override the trunk
# 2. Commit work on this layer
git add . && git commit
# 3. New layer on top (must be run from the topmost branch)
gh stack add api-routes
# ... commit ...
gh stack add frontend
# ... commit ...
# 4. Push all branches and create the linked PRs
gh stack submit
gh stack init enables git rerere automatically, so conflict resolutions are
remembered across rebases. Passing multiple branch names to init adopts
existing branches and creates missing ones — this is also the recovery path
after unstacking.
gh stack add can stage and commit in one step: gh stack add -Am "Add login"
stages everything, commits, and auto-generates a date-slug branch name (e.g.
03-24-add_login). -u stages tracked files only; -A and -u are mutually
exclusive and both require -m.
Submitting
gh stack submit pushes all branches, creates a PR per branch with the correct
base chaining, and links them into a stack on GitHub. Interactively it opens a
full-screen editor (select branches, draft titles/descriptions, toggle draft
state; Ctrl+S submits). Non-interactive contexts and --auto skip the
editor; with --auto, new PRs are created as drafts unless --open is
passed. If all PRs in a stack have merged, submit starts a fresh stack rooted
at the trunk for the unmerged branches.
For agents: prefer gh stack submit --auto (add --open if the PRs should be
ready for review), since the interactive editor needs a TTY.
For convergence tasks, publication is not review completion. When the published
draft satisfies the requested CI/readiness conditions, execute the authorized
gh pr ready <pr> --repo <owner/repo> and read back its state. Where hosted
CodeRabbit review is requested, use
comenq-coderabbit and track request delivery,
completed review and reviewed SHA separately. Local CLI review is
supplementary, not a substitute or an unbounded extra prerequisite. Disposition
substantive findings against the published candidate; distinguish an
unpublished repair from a resolved finding. Required checks and protections
still apply alongside hosted review.
Editing a lower layer
Make the change in the branch it belongs to, not the top:
gh stack down # or: gh stack checkout <branch>
git add . && git commit
gh stack rebase --upstack # cascade the change into the layers above
gh stack push # --force-with-lease per branch
gh stack top # return to where you were
The commands above are separate checkpoints: audit and gate each rewritten candidate before the push. If only a lower layer is validated, use the partial-delivery procedure instead of publishing unvalidated descendants.
Establish replay evidence before synchronization
Before a command that may rewrite, publish or prune stack branches, apply the rebase skill's boundary and acceptance checks. Record each layer's old head, exclusive inherited boundary, target and parent PR identity. Inspect the managed stack metadata before synchronization; do not bypass it with an unrecorded ad-hoc rebase. A merged parent's squash SHA is a landing record, not the child's exclusive boundary. Preserve useful historical refs before pruning.
Do not use gh stack sync --prune as a discovery command: it can rebase, push
and remove evidence before the proposed ranges receive review. If the tool
cannot expose a reviewable plan or preserve the required boundaries, stop that
operation and assess the bounded recovery route in
partial-stack delivery. Do not improvise a
stack-wide adapter or treat a successful exit as acceptance evidence. After
replay, audit the exact old/new series and rerun candidate-bound gates before
accepting or publishing the new stack. Prefer separated rebase and push
operations when that separation is necessary to enforce the acceptance boundary.
Keeping in sync
gh stack sync in one command: fetch → reconcile the remote stack →
fast-forward trunk → cascading rebase (only if trunk moved) → push → sync PR
state → link the stack → prune prompt (interactive terminals only). It never
opens PRs (that is submit's job). A clean remote-ahead update (PRs added on
GitHub on top of the local stack) is pulled down without prompting; a genuine
divergence aborts the sync in non-interactive terminals without pushing
anything.
That abort is a safety net against a diverged remote, not proof of replay
ownership. It says nothing about which commits each layer owns, so it cannot
detect a cascading rebase that replays inherited parent work or drops a child
commit. Establish the replay evidence documented above before running sync
unattended.
After a bottom PR merges, preserve old heads and inspect freshly fetched PR
heads, bases and stack membership first: GitHub may already have replayed
descendants. Compare their ancestry and patches before deciding whether local
replay is still needed. Revalidate rewritten heads; old green checks do not
transfer. Keep merged-parent refs until dependent boundaries are accounted for.
gh stack sync --prune is a later synchronization/cleanup operation, not the
first discovery step after merge.
While a prerequisite is still changing, preserve independent child patches and avoid repeatedly replaying the whole stack onto provisional foundation SHAs. Schedule the final replay against its verified landing. A necessary provisional integration experiment is separate evidence, not the authoritative delivery head.
If sync detects a rebase conflict, it restores all branches untouched and
instructs the operator to run gh stack rebase interactively.
Diverged stacks (neither local nor remote is a clean prefix of the other):
interactive sync offers three options — adopt the remote as source of truth,
delete the stack object on GitHub (then recreate with gh stack submit, running
gh stack modify first if restructuring), or cancel.
Rebasing and conflicts
gh stack rebase fetches, then rebases each branch onto the tip of the one
below, from the trunk upward. --downstack limits it to trunk→current,
--upstack to current→top, --no-trunk skips fetching and the trunk rebase.
Branches whose PR has merged are replayed with --onto automatically.
On conflict (exit code 3), the rebase pauses and lists conflicted files:
# resolve the <<<<<<< markers, then:
git add .
gh stack rebase --continue
# or restore everything to the pre-rebase state:
gh stack rebase --abort
The website's Rebase stack button performs the same cascade server-side,
but those commits are not signed — if the repository requires signed
commits, always rebase locally with gh stack rebase and push with
gh stack push.
Restructuring (gh stack modify)
Interactive TUI for drop (x), fold down/up (d/u), insert (i/I), rename
(r), reorder (Shift+↑/↓), undo (z). Changes are staged and applied
together on Ctrl+S. Reordering and structural changes cannot mix in one
session. Preconditions: active stack checked out, clean working tree, no rebase
in progress, no PR queued, linear history. Recovery: --continue after
resolving an apply-phase conflict, --abort to restore the pre-modify snapshot
(works even after a crash). After modifying, run gh stack submit to push and
recreate the stack on GitHub.
gh stack modify needs a TTY. The non-interactive alternative is:
gh stack unstack → gh stack init <branches in new order> →
gh stack submit.
Merging
gh stack merge # interactive: choose PRs, method, confirm
gh stack merge 42 # merge everything up to and including PR 42
gh stack merge 7 # merge stack number 7 (pure remote operation)
gh stack merge --yes --squash
Without a merge queue, GitHub merges the selected range as one all-or-nothing operation: if any selected PR is unmergeable, none merge. Each selected PR must be open and not a draft. With a merge queue, the selected PRs are added together and method flags are ignored; they may land in separate merge groups.
Select only the eligible contiguous prefix, not an unready upper layer that
blocks the entire selection. Before merging, verify the published head, full
check rollup, required checks, review dispositions and current remote topology;
neither reviewDecision nor gh pr checks --required alone is sufficient.
After merge, verify the landing SHA and assign the successor immediately.
Record merged-base CI separately from the PR-head checks.
If a last remaining PR has no remote stack membership, local stack metadata
does not make gh stack merge applicable. Verify that topology, then use the
authorized ordinary protected PR merge with an exact-head guard, such as
gh pr merge <pr> --match-head-commit <sha> --repo <owner/repo> with the
repository's permitted merge method. Never use an administrator bypass.
Interop with other tools (gh stack link)
For branches managed with Jujutsu, Sapling, git-town, etc. — creates or updates the stack on GitHub with no local tracking:
gh stack link feat-a feat-b feat-c # bottom → top order
gh stack link 7 48 feature-ui # append to existing stack number 7
Branches are pushed automatically; missing PRs are created with correct base
chaining; wrong bases on existing PRs are corrected. Updates are additive only —
link never removes PRs from a stack.
Troubleshooting quick hits
- Merge blocked: check reviews/checks on the PR and every PR below it. If history is not linear, establish replay evidence, rebase, audit and gate the rewritten heads before publication; do not chain rebase straight to push.
- Closed a mid-stack PR: everything above it is blocked. Unstack (from
the website or
gh stack unstack), restructure, and recreate. - PR ejected from the merge queue: all PRs above it are ejected too; re-add the stack once fixed.
- Exit codes worth branching on: 2 not in a stack, 3 rebase conflict,
6 branch belongs to multiple stacks, 9 stacked PRs not enabled for the
repository. Full table in
references/cli-reference.md.
Agent guidance
- Prefer
--auto,--yes, and explicit branch-name arguments;submit(editor),modify,switch, and no-argumentcheckoutneed a TTY. - Run
gh stack view --jsonto inspect stack state programmatically. syncaborts on divergence, but that does not prove replay ownership. Establish the boundary, recovery and publication evidence above first.- Prefer
gh stack pushwhen every branch it will publish is validated. Never use unconditionalgit push --force. For a validated frontier in a partially repaired stack, use the explicit-lease partial-delivery procedure.