Agent Skills: GitHub Stacked Pull Requests (`gh stack`)

>

UncategorizedID: leynos/agent-helper-scripts/github-stacks

Install this agent skill to your local

pnpm dlx add-skill https://github.com/leynos/agent-helper-scripts/tree/HEAD/skills/github-stacks

Skill Files

Browse the full folder contents for github-stacks.

Download Skill

Loading file tree…

skills/github-stacks/SKILL.md

Skill Metadata

Name
github-stacks
Description
>-

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-argument checkout need a TTY.
  • Run gh stack view --json to inspect stack state programmatically.
  • sync aborts on divergence, but that does not prove replay ownership. Establish the boundary, recovery and publication evidence above first.
  • Prefer gh stack push when every branch it will publish is validated. Never use unconditional git push --force. For a validated frontier in a partially repaired stack, use the explicit-lease partial-delivery procedure.