rebase-stacked-pr
Rebase a stacked PR onto main after its parent was squash-merged, using `git rebase --onto` to skip already-merged commits. Use when rebasing a stacked or dependent PR, handling a squash-merged parent PR, or recovering from empty commits or spurious conflicts after `git rebase main`.
Works with
---
name: rebase-stacked-pr
description: Rebase a stacked PR onto main after its parent was squash-merged, using `git rebase --onto` to skip already-merged commits. Use when rebasing a stacked or dependent PR, handling a squash-merged parent PR, or recovering from empty commits or spurious conflicts after `git rebase main`.
license: MIT
---
# Rebase a stacked PR after squash-merge
This skill handles the mechanical rebase when a child PR's parent PR has been squash-merged to `main`. A naive `git rebase main` fails or produces conflicts because the child branch still contains the parent's pre-squash commits, which are logically identical to — but SHA-different from — the new squashed commit on `main`.
The fix is `git rebase --onto main <pre-squash-parent-tip> <child-branch>`, which replays only the child's own commits and skips the already-merged ones.
## Do not use this skill if
- The parent PR has not yet been merged — just rebase onto the parent branch normally (`git rebase origin/parent-branch`).
- The parent PR was merge-committed (not squash-merged) — a plain `git rebase main` works fine in that case.
## Inputs required
Before running commands, confirm:
1. **Parent PR number** — see [Finding the parent PR number](#finding-the-parent-pr-number).
2. **Child branch name** — usually the current branch; verify with `git branch --show-current`.
3. **Parent is actually merged** — check with `gh pr view <PARENT_PR> --json state,mergedAt`. Abort if state is not `MERGED`.
If the child PR description contains a "Rebase instructions" block with explicit variables, prefer those values.
## Finding the parent PR number
Check the child PR description for explicit declarations first:
- `Depends on #`, `Stacked on #`, `Parent: #`
- A `Related` or `Related issues` section — common in this codebase. **Filter to PRs only**; issue numbers referenced there are not valid parents.
Any `#NNNN` reference may be either a PR or an issue. Confirm it is a PR:
```bash
gh pr view <N> --json headRefName,state # errors if N is an issue, not a PR
```
**Strongest signal — matching branch names.** The parent PR's head branch should equal the child PR's base branch. Cross-check every candidate:
```bash
CHILD_BASE=$(gh pr view --json baseRefName -q .baseRefName) # child PR on current branch
gh pr view <CANDIDATE> --json headRefName -q .headRefName
```
If those match, the candidate is almost certainly the parent.
Alternatively, search for PRs whose head branch matches the child's base:
```bash
gh pr list --state merged --head "$CHILD_BASE" --json number,title,url
```
**Ask the user** if any of the following hold — do not guess:
- No explicit marker is present and no candidate branch matches.
- Multiple PRs plausibly match.
- You are not completely certain the candidate is the intended parent.
## Procedure
```bash
# 1. Resolve the pre-squash tip of the parent PR.
# This is the last commit on the parent branch before it was squashed and merged.
OLD_PARENT_TIP=$(gh pr view "$PARENT_PR" --json commits -q '.commits[-1].oid')
# 2. Make sure local main is up to date.
git fetch origin main
# 3. Make sure the child branch is checked out and clean.
git status --porcelain # must be empty
git checkout "$CHILD_BRANCH"
# 4. Replay only the child's commits onto main.
git rebase --onto origin/main "$OLD_PARENT_TIP"
```
## Verification
After the rebase, the log should show **only** the child PR's commits between main and HEAD:
```bash
git log --oneline origin/main..HEAD
```
If that list contains commits that belong to the parent PR (check their messages against `gh pr view $PARENT_PR --json commits`), the rebase replayed already-merged work — undo it and investigate.
The rebase has already completed at this point, so `git rebase --abort` is a no-op. Reset back to the pre-rebase tip, which Git records in `ORIG_HEAD`:
```bash
git reset --hard ORIG_HEAD
```
If `ORIG_HEAD` has been overwritten by a later operation, recover the pre-rebase commit from the reflog:
```bash
git reflog # find the entry just before "rebase (start)"
git reset --hard <sha> # or: git reset --hard HEAD@{N}
```
Note: `git rebase --abort` is only valid **during** an in-progress rebase (mid-conflict). For that case, see [Handling conflicts](#handling-conflicts) below.
## Pushing
Once verification passes:
```bash
git push --force-with-lease
```
Always `--force-with-lease`, never plain `--force` — lease protects against overwriting a teammate's push to the same branch.
## Handling conflicts
`--onto` skips already-merged commits but does **not** prevent real conflicts when `main` has moved and touched the same lines as the child branch.
- If `git rebase --onto` stops with a conflict, do **not** resolve blindly.
- Surface the conflicting files to the user and ask for guidance before editing.
- The user may prefer `git rebase --abort` and a different strategy (e.g., merge rather than rebase, or resolving on a throwaway branch first).
## Edge cases
- **Parent branch was force-pushed during review**: `gh pr view --json commits` returns the *final* commit list of the PR at merge time, which is what you want. Don't use the local `origin/parent-branch` ref — it may point at an obsolete tip.
- **Deep stack (3+ levels)**: rebase bottom-up, one level at a time. After the bottom child is rebased and pushed, treat the next level up as a new child whose parent is now the rebased branch.
- **`gh` not authenticated**: fall back to asking the user for the pre-squash tip SHA, which is visible on the closed PR page as the last commit before the merge event.
## PR description template (for authors)
Authors of stacked PRs should include this block in the PR description so future agents (and humans) can run the rebase without context:
```markdown
## Stack
- Depends on: #<PARENT_PR> (must merge first)
- Parent branch: `<parent-branch-name>`
## Rebase instructions (for agents)
When #<PARENT_PR> is squash-merged to main, rebase this branch with:
PARENT_PR=<PARENT_PR>
CHILD_BRANCH=<this-branch-name>
OLD_PARENT_TIP=$(gh pr view $PARENT_PR --json commits -q '.commits[-1].oid')
git fetch origin main
git checkout $CHILD_BRANCH
git rebase --onto origin/main $OLD_PARENT_TIP
Verify: `git log --oneline origin/main..HEAD` should show only this PR's commits.
Then: `git push --force-with-lease`.
```More Git Workflows skills
git-commit
github/awesome-copilot
Execute git commit with conventional commit message analysis, intelligent staging, and message generation. Use when user asks to commit changes, create a git commit, or mentions "/commit". Supports: (1) Auto-detecting type and scope from changes, (2) Generating conventional commit messages from diff, (3) Interactive commit with optional type/scope/description overrides, (4) Intelligent file staging for logical grouping
git-workflow-and-versioning
addyosmani/agent-skills
Structures git workflow practices. Use when making any code change. Use when committing, branching, resolving conflicts, or when you need to organize work across multiple parallel streams. Use when cutting a release, choosing a semantic version bump, tagging, or writing a changelog.
resolve-merge-conflicts
warpdotdev/common-skills
Resolve Git merge conflicts by extracting only unresolved paths, conflict hunks, and compact diffs instead of loading whole files into context. Use when a merge, rebase, cherry-pick, or stash pop stops on conflicts, when `git status` shows unmerged paths, or when files contain conflict markers.

