Stacked Pull Requests
<metadata>- Scope: Advanced stacked PR workflows and patterns for large features
- Load if: Creating stacked PRs, working on PR stacks, managing dependent PRs
- Prerequisites:
@smith-gh-pr/SKILL.md,@smith-git/SKILL.md,@smith-gh-cli/SKILL.md
CRITICAL (Primacy Zone)
<forbidden>- NEVER merge child PR before parent
- NEVER merge main directly into child branch
- NEVER create stacks deeper than 3-4 levels
- NEVER use squash merge for non-final PRs in a stack
Stack Scope Verification
<required>Before stack-wide operations (rebase cascade, PR creation):
- Load stack metadata from Serena memory (if available)
- Enumerate ALL branches with commit counts
- Present scope summary to user
- Get explicit scope approval before proceeding
- After completion, report status per branch
Empty rebase detection:
- If
git rebaseproduces 0 new commits, STOP - Investigate why (already up-to-date? wrong base?)
- Report the anomaly to user before continuing
For large features, use stacked PRs to maintain atomic, reviewable changes.
When to stack:
- Feature requires 500+ lines of changes
- Multiple logical components that can be reviewed independently
- Need to unblock dependent work before full feature is ready
Creating Stacked PRs
<required>How to stack:
- Create base PR with foundation (e.g.,
feat/auth-base) - Create child PR branching from base (e.g.,
feat/auth-loginfromfeat/auth-base) - Each PR should be independently reviewable and mergeable
- Merge bottom-up: base first, then children
Stack structure:
main
└── feat/auth-base (PR #1: models, migrations)
└── feat/auth-login (PR #2: login endpoint)
└── feat/auth-oauth (PR #3: OAuth integration)
PR description for stacked PRs:
## Stack
- **Depends on**: #123 (feat/auth-base) ← This PR requires #123 to be merged first
- **Blocks**: #125 (feat/auth-oauth) ← PR #125 depends on this PR
Field meanings:
Depends on: PRs that must merge before this one (upstream dependencies)Blocks: PRs waiting for this one to merge (downstream dependents)
Stacked PR Merge Workflow
<required>Sequential merge order (bottom-up):
- Wait for parent PR approval
- Merge parent PR into
main - Rebase child PR onto updated
main - Get child PR approved
- Repeat for each level in stack
Correct merge sequence:
1. Merge PR #1 (feat/auth-base) → main
2. Rebase PR #2 (feat/auth-login) onto main
3. Merge PR #2 → main
4. Rebase PR #3 (feat/auth-oauth) onto main
5. Merge PR #3 → main (can squash this one)
</examples>
Rebasing After Parent Merges
<required>When a parent PR merges, child PRs must be rebased:
- Fetch latest changes
- Checkout child branch
- Rebase onto updated main
- Force push (safe for your PR branch)
git fetch origin
git checkout feat/auth-login
git rebase --onto origin/main feat/auth-base
git push --force-with-lease
Why --onto: Only transplants commits unique to child branch (commits between parent and child), avoiding duplicate commits.
Before rebase (after parent merged):
main ──●──●──●──M (parent merged as M)
\
feat/auth-login ──A──B──C (still based on old parent)
After git rebase --onto origin/main feat/auth-base:
main ──●──●──●──M
\
└──A'──B'──C' (feat/auth-login rebased)
</examples>
Squash Merge with Stacked PRs
<required>Squash merge IS allowed if you follow the branch deletion process for stacked PRs.
Merge Strategy by PR Position:
- Parent (has children): Squash OK with process, delete after child base updated
- Middle: Squash OK with process, delete after child base updated
- Final (leaf): Squash OK, immediate deletion OK
Why squash merge requires extra steps:
Squash merge creates a single commit, destroying commit ancestry. Child branches still contain parent's original commits, causing:
- Duplicate commits in child PR
- Merge conflicts when rebasing
- Git unable to recognize commits already in main
Fixing child PR after parent was squash merged:
Option 1 - Rebase with --fork-point:
git fetch origin
git checkout feat/auth-login
git rebase --onto origin/main --fork-point origin/feat/auth-base
git push --force-with-lease
Option 2 - Interactive rebase to drop parent's commits:
git checkout main && git pull
git checkout feat/auth-login
git rebase -i main
In the interactive editor, mark all commits from the parent branch as drop.
Keeping Stack Updated
<required>When pulling changes from main into a stack, cascade updates through the stack sequentially:
git checkout feat/auth-base
git merge main
git push
git checkout feat/auth-login
git merge feat/auth-base
git push
- After each cascade step, run
git log --oneline -3and confirm the tip commit is the merge/rebase you just performed before proceeding downstream
Merging main directly into a child branch corrupts history:
git checkout feat/auth-login
git merge main
</forbidden>
Best Practices
<examples>Good stack structure:
- Each PR is independently reviewable (clear purpose, focused changes)
- Clear dependency documentation in PR descriptions
- Commits are atomic within each level
- Bottom-up merge order maintained
Good communication:
- Document stack relationships in PR descriptions
- Update child PRs promptly after parent merges
- Notify reviewers when dependencies merge
- Explain the overall feature in parent PR
Bad practices:
- Creating stacks deeper than 3-4 levels (too complex to manage)
- Merging PRs out of order (breaks dependency chain)
- Forgetting to update child PRs after parent merge (causes conflicts)
- Using stacked PRs for unrelated changes (defeats purpose of atomicity)
@smith-gh-pr/SKILL.md- Complete GitHub PR lifecycle@smith-git/SKILL.md- Commits, branches, rebase@smith-gh-cli/SKILL.md- GitHub CLI commands
ACTION (Recency Zone)
<required>Merge stacked PRs bottom-up:
- Merge parent PR first
- Rebase child:
git rebase --onto origin/main feat/parent - Force push child:
git push --force-with-lease - Delete parent branch after child base updated
Verify stack scope before operations:
./smith-stacks/scripts/verify-stack-scope.sh 'feat/PROJ-*'
</required>