---
name: stacking-pull-requests
description: Operates a stack of dependent pull requests in the whiteboard repo with the gh-stack extension — creating a stack across architecture layers, keeping it rebased as main moves, and landing it. Use when a change spans several layers that must merge in order (a shared package under an MCP tool under a web surface), when the user says stacked PR / stack / 積む / スタック, or when a branch's base is another feature branch rather than main. For deciding WHETHER to stack, see the mode table in .claude/rules/dev-flow.md.
---

# Stacking pull requests

`gh stack` (GitHub's stacked pull requests, public preview since 2026-07-30) turns a
chain of dependent branches into one reviewable stack: each PR shows only its own
layer's diff, reviewers work in parallel, and merging is atomic up to whichever PR
you pick.

**Whether to stack at all is a `dev-flow.md` decision, not this skill's.** This skill
is the operating procedure once the answer is yes.

## Prerequisite

```bash
gh extension install github/gh-stack
```

One-time per machine. `gh stack --help` lists every subcommand; the ones below are
the whole day-to-day surface.

## Stack order is not a choice here

This repo's dependency direction is already fixed by `.claude/rules/architecture-map.md`,
and a stack must follow it — a lower layer cannot depend on a higher one:

```
model → codec / canvas-render / ports → workspace
             → server-core → mcp-server | apps/web
```

The composition roots (`mcp-server`, `apps/web`) are therefore always at the TOP of a
stack, never the bottom. That is what makes "split the MCP, CLI and web parts into
separate PRs" mechanical rather than a judgement call: the shared package goes first,
the surface that consumes it goes above it.

Two consequences worth stating:

- **Each PR loads its own layer's review criteria.** `.claude/rules/package-*.md` are
  path-scoped, so a `model` PR carries the Zod-discipline rules while an
  `apps/web` PR carries the accessibility and reachability ones. The per-layer review
  concerns follow the stack for free.
- **The bottom layers answer `userReach` with `foundation:`, and the named follow-up
  is the PR above.** That is the strongest form of that sentinel — a reviewable
  reference instead of "wired later".

## Creating a stack

Bottom-up, one branch per layer:

```bash
gh stack init render-scene-digest mcp-export-digest   # bottom first, then above it
gh stack view                                          # confirm the order
```

Work on the bottom branch, commit, then move up:

```bash
gh stack bottom          # or: gh stack down / up / top / trunk to navigate
# ...edit, commit...
gh stack up
# ...edit, commit...
```

To append a layer later:

```bash
gh stack add apps-web-digest-panel
```

Then publish the whole stack:

```bash
gh stack submit
```

Interactive by default: a single editor screen where you title each PR and choose
draft state, submitted together with `Ctrl+S`. `--auto` skips the editor and creates
drafts with generated titles — **do not use `--auto` here**, because every title is a
future squash-commit message and must be a Conventional Commit (see the PR Title Rule
in AGENTS.md). `gh stack submit` re-run later updates existing PRs and their bases.

In a non-interactive session (no editor), `gh stack submit` behaves like `--auto`:
it creates the PR with a generated, non-Conventional title (the branch name with
hyphens as spaces). Fix it immediately over REST (`gh pr edit` is GraphQL-backed, HTTP 403
in a web session): `gh api -X PATCH repos/{owner}/{repo}/pulls/<n> -f title='feat(...): ...'`
— the `pr-title` check listens for the `edited` event, so the corrected title
re-validates on its own; nothing else re-runs. Do NOT `gh run rerun` a failed title
check: reruns re-read the ORIGINAL event payload and can never see the new title.

## Keeping the stack current

When `main` moves:

```bash
gh stack sync
```

That fetches, fast-forwards the trunk, cascade-rebases each branch onto **its parent**
(not onto main), and pushes every branch atomically with `--force-with-lease --atomic`.

Never hand-rebase the layers with `git rebase main` per branch — that rebases each one
onto the trunk instead of its parent, flattening the stack, and the per-branch force
pushes are not atomic. Resolve conflicts bottom-up: a conflict in a lower layer
reappears in every layer above it until it is fixed at the bottom.

`gh stack rebase --no-trunk` does only the inter-branch rebases when the trunk is
already current.

## Landing a stack

```bash
gh stack merge <pr-number> --squash     # everything up to and including that PR
gh stack merge --yes --squash           # the whole stack, no prompt
```

Atomic and all-or-nothing: if any PR in the range cannot merge, none of them do.
Upper PRs that stay behind retarget themselves automatically.

**Always `--squash`.** release-please derives the version bump and changelog from
squash-merge commit subjects, so a merge commit or a rebase merge breaks that contract.

**`gh stack merge` only recognises a stack `gh stack init` created.** A set of
PRs whose base branches merely point at each other is not one: the command
answers `#N is not a stack number or a stacked pull request`, and you are
left merging them one at a time. Worse, each squash rewrites the layer below
into a commit the upper branch has never seen, so the next PR turns
`CONFLICTING` the moment its predecessor lands and needs `origin/main`
merged back in by hand. Verified twice. Decide you are stacking BEFORE you
push the first branch.

Two limits of the preview, both verified against `gh stack merge --help`:

- **Bypassing merge requirements is not supported for stacks.** Branch protection and
  repository rules are evaluated by GitHub when the merge runs. The local
  `LEFTHOOK=0` habit is unrelated — that is a pre-push hook, not a merge requirement.
- Merge-queue support is still rolling out. On a queued base branch the stack is added
  to the queue rather than merged directly.

## Repo-specific notes

**The PR-creation guard does not fire on `gh stack submit`** — it reads only
`gh pr create` and the REST form of it (`gh api -X POST …/pulls`). That is correct rather than
a gap: the guard blocks a branch that is behind `origin/main`, and every layer above the bottom of a stack is
legitimately behind main because it is based on the layer below. Keep the *bottom* of
the stack current with `gh stack sync`; the rest follows.

**Stacks are a single-worktree flow.** `dev-flow.md`'s parallel model gives each
concurrent dev-loop its own worktree because those lanes are scope-disjoint. A stack is
the opposite: deliberately dependent and linear. Build it in one checkout and navigate
with `gh stack up` / `down` rather than creating a worktree per layer.

**Per-layer verification still applies.** Each PR is reviewed as its own change, so
each one carries its own nearest-layer tests. A stack does not let a lower layer defer
its tests to the PR above it.

## Reference

- `evaluations.md` — the three scenarios this skill exists to make succeed, with the
  baseline failure each one closes.
- `gh stack <command> --help` — authoritative for flags; this file covers the path.
- Documentation: https://gh.io/stacks
