Stacked Pull Requests skill for Codex
Small dependent PRs that merge bottom-up cleanly. Codex designs a stack where every layer builds, passes CI and is safe to deploy alone, then creates it with the team's tool: plain git with --update-refs, GitHub's gh stack extension, or Graphite. It restacks after review fixes without interactive editors, handles the squash-merge case with rebase --onto, fixes CI filters that skip non-main bases, and asks before every force push or PR creation.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- stacked-pull-requests Installs to ~/.agents/skills/stacked-pull-requests/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install stacked-pull-requests The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project stacked-pull-requests Installs to .agents/skills/stacked-pull-requests/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/stacked-pull-requests.zipunzip -o stacked-pull-requests.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) stacked-pull-requests PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $stacked-pull-requests, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Splits a large change into a stack of small dependent pull requests and keeps it healthy - plain git with rebase --update-refs, GitHub native stacks via the gh stack extension, or Graphite's gt CLI - covering branch layout, restacking after review fixes, force-with-lease pushes, retargeting after squash merges, CI that runs on non-main bases, and stack descriptions. Use when a feature is too big for one reviewable PR, when asked to stack or split PRs, when a lower PR changed and the branches above need rebasing, or when a squash merge broke the rest of a stack.
Use it when
- A feature is too large to review as one PR
- A lower branch in a stack changed and the rest must follow
- A squash or rebase merge left the remaining stack conflicting
- Middle PRs of a stack are not running CI
Use something else when
- You are rewriting commits within a single branch: use git-history-surgery
- You are writing the PR description itself: use pull-request-writing
- You want parallel independent branches checked out at once: use git-worktrees
How to check the result
git log --graph --decorate shows each branch label on its own layer with no duplicate commits, each PR's diff contains only its layer, and CI passed on every PR in the stack.
The mistake it prevents
Restacking with a plain rebase after a squash merge, which replays the already-merged commits and floods the next PR with conflicts; use rebase --onto with the old parent tip.
A healthy stack is a chain of PRs where each one is small enough to review in one sitting (roughly under 400 changed lines), passes CI on its own, is safe to deploy if merging stopped there, and is merged strictly bottom-up. Every branch push that rewrites history is shown to the user first.
Before stacking
- Read
AGENTS.mdandCONTRIBUTING.mdfor merge method (squash, rebase, merge commit) and branch naming. The merge method decides how you restack after each merge. - Check what the team already uses:
gh extension list(gh stack),which gt(Graphite), a.graphite_repo_config, orjjin the repo. Use their tool; stacks built with a different tool confuse reviewers. - Confirm
git statusis clean andgit config rebase.updateRefs(set it totruefor plain-git stacks so rebases move every branch in the stack).
Design the split
Order layers so each PR compiles, passes tests and changes no user-visible behaviour until the last one, or hide behaviour behind a flag:
| Pattern | Stack |
|---|---|
| New feature end to end | 1 schema/migration, 2 domain logic + tests, 3 API, 4 UI behind a flag, 5 flag on |
| Refactor then feature | 1 mechanical refactor (no behaviour change), 2 feature using the new shape |
| Large rename or codemod | 1 the tool/script, 2 the generated change, 3 manual follow-ups |
| Risky dependency bump | 1 compatibility shims, 2 the bump, 3 cleanup of shims |
Keep generated and mechanical changes in their own PR; reviewers can skim them. Never put a reviewer-critical change inside a 2,000-line mechanical PR.
Tool choice
| Situation | Use |
|---|---|
| GitHub repo, team has native stacked PRs enabled | gh stack extension (gh stack init --base main, gh stack rebase, gh stack push; gh stack --help for the rest) |
| Team uses Graphite | gt create, gt modify, gt submit --stack, gt sync, gt restack |
| No tooling, or a one-off | Plain git + gh pr create --base <parent>, described below |
| Gerrit or Phabricator-style review | Not this skill’s model; follow that system’s change-per-commit flow |
GitHub native stacks evaluate required reviews, checks and CODEOWNERS against the stack’s
base branch and keep linear history with gh stack rebase. They require all branches in
one repository (no forks).
Plain git workflow
Build the stack
git switch -c feat/orders-1-schema origin/main
# ...commit...
git switch -c feat/orders-2-service # branches from the previous branch
# ...commit...
git switch -c feat/orders-3-api
# ...commit...
git log --oneline --decorate --graph origin/main..HEAD # every branch label visible, in order
Open the PRs (outward-facing: confirm titles and bodies with the user first)
git push -u origin feat/orders-1-schema feat/orders-2-service feat/orders-3-api
gh pr create --base main --head feat/orders-1-schema --title "Orders 1/3: schema" --body-file pr1.md
gh pr create --base feat/orders-1-schema --head feat/orders-2-service --title "Orders 2/3: service" --body-file pr2.md
gh pr create --base feat/orders-2-service --head feat/orders-3-api --title "Orders 3/3: API" --body-file pr3.md
Each PR’s base is its parent branch, so reviewers see only that layer’s diff. Put the stack map at the top of every body:
**Stack** (merge bottom-up)
1. #101 Orders 1/3: schema
2. #102 Orders 2/3: service <- this PR
3. #103 Orders 3/3: API
Depends on #101. Safe to deploy alone: yes (service not wired to any route yet).
Address review feedback on a lower PR
Make the fix where it belongs, then move everything above it:
git switch feat/orders-1-schema
# ...edit, commit (or: git commit --fixup=<sha>)...
git switch feat/orders-3-api # the top of the stack
git rebase --update-refs feat/orders-1-schema # replays 2 and 3 onto the new 1, moves branch 2 too
For fixups made at the top that belong lower down, squash them in place without an editor (Codex cannot drive interactive rebases):
git commit --fixup=<sha-in-branch-1>
GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash --update-refs origin/main
Then push every rewritten branch. This is a force push: show the branch list and get a yes before running it.
git push --force-with-lease --force-if-includes origin \
feat/orders-1-schema feat/orders-2-service feat/orders-3-api
After the bottom PR merges
git fetch origin
| Merge method used for the bottom PR | Restack the remainder |
|---|---|
| Merge commit or rebase merge | From the top: git rebase --update-refs origin/main; already-merged commits drop out |
| Squash merge | The squashed commit has a new SHA, so the old commits would conflict. From the top: git rebase --update-refs --onto origin/main feat/orders-1-schema (uses the old branch 1 tip as the cut point; do this before deleting that local branch) |
Then retarget the next PR if GitHub did not do it automatically (it does when the merged
branch is deleted): gh pr edit 102 --base main. Push the rewritten branches with
--force-with-lease after confirmation, and delete the merged local branch.
Graphite equivalents
gt create -am "Orders 1/3: schema" # new branch on top of the current one, with a commit
gt modify -a # amend the current branch and restack everything above
gt submit --stack # push and create/update PRs for the whole stack
gt sync # pull trunk, delete merged branches, restack
gt log short # see the stack
gt submit pushes and opens PRs, so confirm with the user before running it.
CI and protection gotchas
- Workflows filtered with
on: pull_request: branches: [main]do not run for PRs whose base is another feature branch. Remove the filter or add the stack’s branch pattern, otherwise middle PRs merge untested. (GitHub native stacks run checks as if each PR targets the base.) - Merging an upper PR first merges it into its parent branch, not into main. Its code then arrives in main unreviewed against main’s rules. Always merge bottom-up; consider enabling auto-merge only on the bottom PR.
- Required approvals usually reset on force push when “dismiss stale reviews” is on. Batch restacks instead of pushing after every small fix.
- Stacks deeper than about five PRs spend more time restacking than reviewing. Merge the bottom early, or split into two independent stacks.
--force-with-leasewithout a recent fetch can still clobber a teammate’s push to a shared branch;--force-if-includesguards against that. Never force push a branch someone else is committing to without asking.
Verify
git log --oneline --graph --decorate origin/main..<top>shows each branch label on the expected commit, in order, with no duplicated commits.- Each PR’s “Files changed” contains only its layer (check with
gh pr diff 102 --name-only). - CI ran and passed on every PR in the stack, not only the bottom one.
Report: stack map with PR numbers, what each layer changes, merge method and restack plan, and any branch that was force-pushed (with the user’s approval).
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Stacked Pull Requests"
short_description: "Small dependent PRs that merge bottom-up cleanly"
default_prompt: "$stacked-pull-requests This branch is 3,000 lines. Split it into a stack of reviewable PRs" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.