Rebase Onto Main
Fetch and rebase the current feature branch onto the repository's base branch.
Options
The user may provide these options inline:
- --base
<branch>: Override the auto-detected base branch (e.g.,--base develop)
Workflow
1. Pre-Flight Checks
Run these commands in parallel to understand the current state:
# Check for uncommitted changes
git status
# Detect the repository's default branch
gh repo view --json defaultBranchRef -q '.defaultBranchRef.name'
# Confirm which branch we are on
git branch --show-current
# Check whether the branch has been pushed to a remote
git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || echo "no upstream"
If --base <branch> was specified, use that value instead of the detected default branch.
If gh is not available, fall back to detecting the default branch with:
git remote show origin | grep 'HEAD branch' | sed 's/.*: //'
If the current branch is the default branch itself, warn the user that rebasing the base branch onto itself is a no-op and stop.
2. Handle Uncommitted Changes
If git status shows uncommitted changes (staged or unstaged):
- Warn the user that there are uncommitted changes.
- Ask whether to:
- Stash: Run
git stashbefore proceeding, thengit stash popafter the rebase completes. - Commit first: Invoke the
/commitskill, then continue with the rebase. - Abort: Stop without doing anything.
- Stash: Run
Rebase will refuse to start with a dirty working tree, so this step is mandatory before fetching.
3. Fetch and Rebase
Capture the current HEAD so the post-rebase step can detect whether history was actually rewritten:
git rev-parse HEAD
Record this as <pre-rebase-head>. Then:
git fetch origin <default-branch>
git rebase origin/<default-branch>
Where <default-branch> is the detected or overridden base branch name.
4. Handle Rebase Result
Clean Rebase
If the rebase completes without conflicts:
- Report success.
- Show a summary of the replayed commits:
git log origin/<default-branch>..HEAD --oneline
This range lists exactly the commits that now sit on top of the rebased base, regardless of whether the branch has an upstream. Avoid @{u}..HEAD here: it fails when the branch has no upstream, and after a rebase it can also include base-branch commits the rebase moved onto, not just the replayed feature commits.
Already Up to Date
If git reports "Current branch is up to date." or "Fast-forwarded ...", report the result and continue to the post-rebase steps. In this case the local HEAD is unchanged (or only fast-forwarded), so the push step uses a normal git push rather than --force-with-lease.
Conflicts
If the rebase produces conflicts, proceed to the conflict resolution workflow below.
5. Conflict Resolution
Rebase resolves conflicts per commit, not as a single merge. For each conflicting commit, git pauses the rebase, marks the conflicting files, and waits for resolution before continuing.
For each pause:
- Identify the conflicting commit:
git status
The status output names the commit currently being applied (e.g., "You are currently rebasing branch '...' on ''."). Read it and look at the commit being replayed:
git log -1 --oneline REBASE_HEAD
- List conflicted files:
git diff --name-only --diff-filter=U
Resolve each conflicted file:
- Read the file and examine the conflict markers (
<<<<<<<,=======,>>>>>>>). - Use the surrounding code context, the intent of both sides, and the project's conventions to determine the correct resolution.
- For trivial conflicts (whitespace, import ordering, adjacent non-overlapping changes), resolve automatically.
- For non-trivial conflicts where the correct resolution is ambiguous, show the user both sides and ask which to keep or how to combine them.
- Read the file and examine the conflict markers (
Stage resolved files:
git add <resolved-file>
- Continue the rebase:
git rebase --continue
This re-applies the commit with the resolved content. Git will reuse the original commit message; if the commit becomes empty after resolution, it prompts to skip with git rebase --skip.
- Repeat until git reports the rebase complete. Each subsequent commit may produce its own conflicts.
If at any point the situation becomes unrecoverable, abort cleanly:
git rebase --abort
This restores the branch to its pre-rebase state.
6. Post-Rebase Steps
After a successful rebase (with or without conflict resolution):
Lockfile changes: If any lockfiles changed during the rebase (e.g.,
package-lock.json,yarn.lock,pnpm-lock.yaml,go.sum,Gemfile.lock,poetry.lock,Cargo.lock,composer.lock), suggest running the appropriate install command:package-lock.json->npm installyarn.lock->yarn installpnpm-lock.yaml->pnpm installgo.sum->go mod tidyGemfile.lock->bundle installpoetry.lock->poetry installCargo.lock->cargo buildcomposer.lock->composer install
Push:
First decide whether a force push is actually required. A change in HEAD alone is not enough to conclude that history was rewritten: when the local branch had no commits beyond the base,
git rebasesimply fast-forwards HEAD onto the new tip without rewriting anything. Distinguish the two cases by checking whether<pre-rebase-head>from step 3 is still reachable from the post-rebase HEAD:git rev-parse HEAD git merge-base --is-ancestor <pre-rebase-head> HEADIf
git merge-base --is-ancestorexits 0, the pre-rebase commit is an ancestor of the new HEAD, so the rebase was a no-op or a fast-forward and no force push is needed. If it exits non-zero, the original commits are no longer on the branch's history line, so the rebase rewrote history and any already-published copy of the branch must be replaced with a force push.Choose the push command based on three cases:
No upstream (fresh branch never pushed): no force needed.
git push -u origin HEADUpstream exists, no-op or fast-forward only (
<pre-rebase-head>is an ancestor of the post-rebase HEAD): history was not rewritten. Skip the push entirely if HEAD is unchanged, or run a plaingit pushif there are local commits ahead of upstream that haven't been pushed yet.git pushUpstream exists, history rewritten by rebase (
<pre-rebase-head>is no longer an ancestor of the post-rebase HEAD): force-with-lease is required to replace the previously pushed history.git push --force-with-lease
Always use
--force-with-lease, never plain--force.--force-with-leaserefuses to overwrite the remote if someone else has pushed in the meantime; plain--forceclobbers their work without checking.The user's CLAUDE.md forbids using force flags as a workaround without explicit instruction. Force-pushing after a rebase that rewrote history is the documented exception: it is required by the rebase workflow itself, not a workaround for an unexpected failure. Still prefer
--force-with-leaseover--forceand surface the command before running it.Never run
git push --force(without--force-with-lease) and never force-push the default branch.
7. Stash Recovery
If changes were stashed in step 2, pop the stash after the rebase completes:
git stash pop
If the stash pop produces conflicts, warn the user and list the conflicted files.
Error Handling
- On the default branch: Warn that rebasing the base branch onto itself is a no-op and stop.
- No remote configured: Report the error and stop.
- Dirty working tree: Rebase refuses to start. Step 2 must resolve this before fetching.
- Rebase aborted by user: Clean up with
git rebase --abort, which restores the pre-rebase state. - Empty commit during rebase (the commit's changes are already present in the base): Use
git rebase --skipto drop it, after confirming with the user. - Fetch failure (network issues, authentication): Report the error clearly and stop.
--force-with-leaserejection (someone else pushed to the remote branch since you last fetched): Do not escalate to--force. Stop, report the divergence, and ask the user how to proceed (typically: fetch, inspect the upstream changes, decide whether to integrate them).- Stash pop conflicts: Warn the user and list conflicted files so they can resolve manually.