gh stack sync vs rebase: Understanding the Difference in GitHub's gh-stack

gh stack sync is a full-cycle remote synchronization command that fetches, pushes, and updates GitHub's stack metadata, while gh stack rebase performs local-only cascading rebases to rearrange commits without network operations.

When managing stacked pull requests with the github/gh-stack extension, choosing between these commands determines whether you are aligning your local repository with remote state or merely fixing local history. Both utilize the same cascadeRebase helper internally, but they differ fundamentally in their scope, network behavior, and side effects.

What Does gh stack sync Do?

The gh stack sync command, implemented in cmd/sync.go within the runSync function, orchestrates a comprehensive synchronization between your local branches and the remote GitHub stack object. According to the command's long help text (lines 27-41), its primary goal is to bring the local stack and remote stack representation into complete agreement.

The implementation follows this sequence:

  1. Fetch the latest remote refs from GitHub
  2. Reconcile any remote-side stack changes via reconcileRemoteStack, pulling down new PR branches if they exist
  3. Fast-forward branches that are behind their remote tracking refs
  4. Cascade-rebase the stack if necessary, invoking the same cascadeRebase helper used by the rebase command
  5. Push all branches atomically with --force-with-lease
  6. Sync PR state and link pull requests into the remote stack object
  7. Prune merged local branches when invoked with --prune

When conflicts occur during the cascade-rebase phase, the command aborts immediately and instructs the user to resolve them using gh stack rebase (lines 51-53). Upon successful completion, the command outputs either "Stack synced" (when the remote object is updated) or "Branches synced" (when only local branches are pushed), as implemented in lines 98-104.

What Does gh stack rebase Do?

Defined in cmd/rebase.go and executed via the runRebase function, gh stack rebase is strictly a local operation that rearranges commit history. As described in lines 53-60, it performs a cascading rebase so that each branch in the stack sits on top of its predecessor (or the trunk) without ever fetching from or pushing to the remote.

Key implementation characteristics include:

  • No network operations: Skips fetching and pushing entirely
  • Cascading execution: Calls cascadeRebase (lines 150-160) to sequentially rebase each branch onto its parent
  • Conflict state management: On conflict, writes a state file named gh-stack-rebase-state (lines 62-66) to allow users to continue or abort later
  • Scope control: Supports --downstack (trunk to current), --upstack (current to top), and --no-trunk flags (lines 48-55) to limit the rebase range

After a successful rebase, the command reminds you to run gh stack push to propagate changes, explicitly avoiding any modification of remote PR metadata.

Key Differences: Remote Synchronization vs Local History

Aspect gh stack sync gh stack rebase
Primary Goal Synchronize local state with remote GitHub stack object Locally rearrange commits via cascading rebase
Network Operations Fetches from remote and pushes with --force-with-lease No fetch or push; local Git operations only
Remote Metadata Updates and links PRs into the remote stack object Does not modify remote PR state or stack file
Conflict Resolution Aborts on conflict; defers to gh stack rebase Saves state to file; supports --continue and --abort
Typical Result Repository matches remote GitHub state exactly Repository rebased locally; requires manual push

When to Use Each Command

Use gh stack sync When You Need to Align with GitHub

Run this command when you need to ensure GitHub reflects your current local work. This includes scenarios where you have updated commits locally and need to push them, when teammates have modified the remote stack and you must reconcile changes, or when you want to prune merged branches using the --prune flag.

Use gh stack rebase When You Need to Fix Local History

Execute this command when resolving diverging histories after pulling from upstream, when reordering commits within your stack before submitting, or when continuing a previously conflicted rebase using --continue. The --downstack and --upstack flags allow you to limit the operation to specific portions of the stack without affecting the entire chain.

Handling Conflicts: Different Workflows

The commands diverge significantly in their approach to merge conflicts. In cmd/sync.go (lines 51-53), when the embedded cascadeRebase call encounters conflicts, gh stack sync immediately aborts the entire operation and directs you to run gh stack rebase for manual resolution.

Conversely, cmd/rebase.go (lines 62-66) handles conflicts by writing the gh-stack-rebase-state file to disk, allowing you to resolve conflicts at your own pace and resume with gh stack rebase --continue or revert entirely with gh stack rebase --abort.

Practical Code Examples


# Full synchronization: fetch, reconcile, rebase, push, and update PRs

gh stack sync
gh stack sync --prune

# Local-only rebase operations

gh stack rebase
gh stack rebase --downstack
gh stack rebase --upstack
gh stack rebase --no-trunk

# Conflict resolution during rebase

gh stack rebase --continue
gh stack rebase --abort

Summary

  • gh stack sync in cmd/sync.go is the comprehensive command that fetches remote changes, reconciles the stack object, pushes branches with --force-with-lease, and updates GitHub metadata
  • gh stack rebase in cmd/rebase.go is the local-only tool for cascading rebases that uses the cascadeRebase helper without network operations
  • Sync aborts on conflicts and requires rebase for resolution; rebase provides --continue and --abort workflows via the gh-stack-rebase-state persistence file
  • Use sync to align local and remote states; use rebase to manipulate local commit history before pushing

Frequently Asked Questions

Can I run gh stack rebase after gh stack sync fails?

Yes. When gh stack sync encounters rebase conflicts during its cascade phase (as implemented in cmd/sync.go lines 51-53), it aborts the entire synchronization and explicitly instructs you to run gh stack rebase to resolve the conflicts manually. After resolving the conflicts and completing the rebase with --continue, you can then run gh stack sync again to push the changes and update the remote stack object.

Does gh stack sync automatically prune merged branches?

Only when invoked with the --prune flag. According to the implementation in cmd/sync.go, the sync command includes an optional pruning step that deletes local branches for PRs that have been merged on GitHub. Without this flag, the command synchronizes active branches but leaves merged branches intact in your local repository.

Why does gh stack rebase not update my pull requests on GitHub?

Because gh stack rebase (defined in cmd/rebase.go lines 53-60) performs strictly local Git operations and deliberately does not push to the remote. After completing the cascading rebase, the command simply prints a reminder to push using gh stack push or gh stack submit. This design allows you to verify the rebased history before exposing rewritten commits to collaborators and updating the remote PR stack metadata.

What happens to the stack state if I abort a rebase?

When you run gh stack rebase --abort, the command removes the gh-stack-rebase-state persistence file and restores the branches to their original positions recorded before the rebase began. This cleanup is managed within the runRebase function in cmd/rebase.go (lines 62-66), ensuring your repository returns to a clean state without partial rebase artifacts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →