Skip to main content

ghstack

Conveniently submit stacks of diffs to GitHub as separate pull requests.

uv tool install ghstack

ghstack is tested with several different Python versions. It requires at least Python 3.9.1.

How to setup

Go to github.com Settings→Developer Settings→Personal Access Tokens and generate a token with public_repo access only. Create a ~/.ghstackrc as shown below:

λ cat ~/.ghstackrc
[ghstack]
github_url = github.com
github_oauth = [your_own_token]
github_username = [your_username]
remote_name = upstream [if remote is called upstream and not origin]
automsg = claude [optional; also supports codex]

You can toggle generated change descriptions with:

ghstack config automsg claude
ghstack config automsg codex
ghstack config automsg codex --model gpt-5.4
ghstack config --unset automsg

If automsg is set to claude or codex, ghstack writes PR context (the local commit message and current PR description) to a temporary file and invokes the agent with a prompt to summarize the specific update. The patch update itself is included directly in the prompt. For existing PRs the patch update is an interdiff against the previously submitted PR patch, not the whole PR patch. Explicit ghstack -m MESSAGE still overrides this.

How to use

Make sure you have write permission to the repo you're opening PR with.

Prepare a series of commits on top of main, then run ghstack. This tool will push and create pull requests for each commit on the stack.

How do I stack another PR on top of an existing one? Assuming you've checked out the latest commit from the existing PR, just git commit a new commit on top, and then run ghstack.

How do I modify a PR? Just edit the commit in question, and then run ghstack again. If the commit is at the top of your stack, you can edit it with git commit --amend; otherwise, you'll have to use git rebase -i to edit the commit directly.

How do I rebase? The obvious way: git rebase origin/main. Don't do a git merge; ghstack will throw a hissy fit if you do that. (There's also a more fundamental reason why this won't work: since each commit is a separate PR, you have to resolve conflicts in each PR, not just for the entire stack.)

What if the repository default branch changed? ghstack caches repository metadata in .git/ghstack-repo-info.json for the local checkout. If ghstack is still using an old default branch name, delete that file and rerun ghstack; it will query GitHub again.

How do I start a new feature? Just checkout main on a new branch, and start working on a fresh branch.

How do I merge my changes? Warning: You will NOT be able to merge these commits using the normal GitHub UI, as their branch bases won't be main. Use ghstack land $PR_URL (or alternatively ghstack land #PR_NUM) to land a ghstack'ed pull request.

You can also setup a GitHub action to allow a bot land. ghstack_land_example is an end-to-end example of how to do this.

Structure of submitted pull requests

Every commit in your local commit stack gets submitted into a separate pull request and pushes commits onto three branches:

  • gh/username/1/base - think of this like "main": it's the base branch that your commit was based upon. It is never force pushed; whenever you rebase your local stack, we add merge commits on top of base from the true upstream main.

  • gh/username/1/head - this branch is your change, on top of the base branch. Like base, it is never force pushed. We open a pull request on this branch, requesting to merge into base.

  • gh/username/1/orig - this is the actual commit as per your local copy. GitHub pull requests never sees this commit, but if you want to get a "clean" commit all by itself, for example, because you want to work on the commits from another machine, this is the best way to get it.

Design constraints

There are some weird aspects about GitHub's design which lead to unusual design decisions on this tool.

  1. When you create a PR on GitHub, it is ALWAYS created on the repository that the base branch exists on. Thus, we MUST push branches to the upstream repository that you want PRs to be created on. This can result in a lot of stale branches hanging around; you'll need to setup some other mechanism for pruning these branches.

  2. Branch name does not correspond to pull request number. While this would be excellent, we have no way of reserving a pull request number, so we have no idea what it's going to be until we open the pull request, but we can't open the pull request without a branch.

Ripley Cupboard

Channeling Conor McBride, this section documents mistakes worth mentioning.

Non-stack mode. ghstack processes your entire stack when it uploads updates, but it doesn't have to be that way; you could imagine that you could ask ghstack to only process the topmost commit and leave the rest alone. An easy and attractive looking way of doing this is to edit the stack selection algorithm to look a single commit, rather than all the commits from merge-base to head.

This sounds OK but you try it and you realize two things:

  1. This is wrong, if you exclude the commits before your commit you'll end up with a base commit based on the "literal" commit in your Git repository. But this has no relationship with the base commit that was previously uploaded, which was synthetically constructed.

  2. You also have do extra work to pull out an up to date stack to write into the pull request body.

So, this is not impossible to do, but it will need some work. You have to work out what the real base commit is, whether or not you need to advance it, and also rewrite the stack rendering code.

Metadata

Release files for ghstack 0.18.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ghstack 0.18.0
File Size Uploaded
ghstack-0.18.0.tar.gz 111.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ghstack 0.18.0
File Interpreter ABI Platform
ghstack-0.18.0-py3-none-any.whl Python 3 none any Details

Total release size: 238.3 kB

Release files / ghstack-0.18.0.tar.gz

Download URL ghstack-0.18.0.tar.gz
Size 111.9 kB
Tags Source
SHA-256 checksum
How to use checksums
533be695a956b6150ed0a932247b222bb3e32b5410783c21cd760e03079c5dbe
BLAKE2b-256 checksum
How to use checksums
019275721df4f74b96fe0ba767961be921b19b180cb2bf9fd3c7a163a74ba78c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.

Transparency log

Release files / ghstack-0.18.0-py3-none-any.whl

Download URL ghstack-0.18.0-py3-none-any.whl
Size 126.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1faf2f3960fce019db2029bae0dbb9a4fab5a955fd9c1470d883d1966d223274
BLAKE2b-256 checksum
How to use checksums
bed78ac59a2bb8e0ea7d01284dc007bfc917ce8219d5927ecb8d905a23961e25
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.18.0 This release

2 release files

0.17.0

2 release files

0.16.0

2 release files

0.13.0

2 release files

0.12.0

2 release files

0.10.0

2 release files

0.9.6

2 release files

0.9.5

2 release files

0.9.4

2 release files

0.9.3

2 release files

0.9.2

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.3

1 release file

0.3.2

1 release file

0.3.1

1 release file

0.3.0

1 release file

0.2.1

1 release file

0.2.0

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.28

2 release files

0.0.27

2 release files

0.0.26

2 release files

0.0.25

2 release files

0.0.24

2 release files

0.0.18

2 release files

0.0.17

2 release files

0.0.16

2 release files

0.0.15

2 release files

0.0.14

2 release files

0.0.13

2 release files

0.0.12

2 release files

0.0.11

2 release files

0.0.10

2 release files

0.0.9

2 release files

0.0.8

2 release files

0.0.7

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page