jj-stack: manage stacked GitHub PRs with jj
jj-stack turns a linear series of local jj changes into a stack of GitHub pull requests.
Rewrite, split, squash, or reorder the changes with jj, then run jj-stack submit to update
GitHub. Existing PRs follow their change IDs, keeping comments and review history together.
Quick start
You need Python 3.14 or newer, jj 0.45.1 or newer, and a repo on github.com where you can push
branches and open pull requests. jj-stack uses GITHUB_TOKEN, then GH_TOKEN, then your GitHub
CLI login for authentication.
Install with uv:
uv tool install jj-stack
Submit your first stack
Start with a linear series of local jj changes on top of trunk(). Authenticate with
gh auth login, or supply a token in GITHUB_TOKEN or GH_TOKEN. Check the repo setup and
apply the safe local fixes:
jj-stack doctor --fix
Inspect the stack that ends at your working copy:
jj-stack
Create one GitHub PR per local change:
jj-stack submit
Revise the changes locally with jj and rerun jj-stack submit whenever the stack is ready to
refresh. Use jj-stack list to see every tracked stack in the repo.
Mental model
Your local jj history determines which changes form a stack and their order. On GitHub, each
change gets a stable PR branch and a PR. The bottom PR targets trunk by default, and each PR
above it targets the PR branch below:
Local changes: trunk() <- A <- B <- C
GitHub PRs: main <- PR #1 <- PR #2 <- PR #3
Each PR's diff shows only the changes it adds on top of its base, so reviewers can consider
one change at a time. jj-stack manages the PR branches for you, and they normally stay out
of your local bookmark view.
When you rewrite a change, its change ID still connects it to the same PR. Submitting again updates that PR and the PRs for any dependent changes, preserving their discussions.
To select a stack, pass the change ID of its head (the top change). jj-stack follows the
head's parents back to trunk to find the rest. If you don't name a head, it uses your working
copy when it has both a description and changes, or its parent otherwise. After editing a
lower change, pass the head's ID to jj-stack submit so the update includes the changes above
your edit.
Everyday workflow
- Write code as a series of local
jjchanges. - Run
jj-stack submit. - Revise, add, remove, or reorder the changes locally as reviews come in.
- Run
jj-stack submitagain to refresh GitHub. - Run
jj-stack merge --pull-request <last-pr-to-merge> --method squashwhen the bottom portion is ready. Choose a merge method your repo allows. Queues choose their own method. - After a queued merge or a merge made through GitHub finishes, run
jj-stack sync <head-change-id>.
view, submit, merge, and sync accept a change ID when you need to select a stack other
than the one ending at the working copy.
See the user guide for drafts, descriptions, merge queues, cleanup, and working with multiple stacks.
Optional setup
Invoke it as jj stack
Add a command alias to your user configuration with jj config edit --user:
[aliases]
stack = ["util", "exec", "--", "jj-stack"]
For tab completion of both jj-stack and jj stack, add the output of jj-stack completion to
your shell startup file:
eval "$(jj-stack completion zsh --jj-alias stack)"
bash and fish work the same way. See
Configuration for more
setup options.
Other installation options
pipx provides another isolated installation:
pipx install jj-stack
You can also use pip inside an activated virtual environment:
python -m pip install jj-stack
To upgrade an installation made with uv, run uv tool upgrade jj-stack. If the command is
not on your shell PATH, run uv tool update-shell.
Learn more
- How jj-stack works
- Submit and update
- Merge and sync
- Multiple stacks
- Configuration
- Writing PR descriptions
- Troubleshooting
- Compare tools
- Automation
For all flags and aliases, use the built-in help:
jj-stack --help
jj-stack <command> --help
jj-stack help --all
Coding agent integration
Install the bundled skill to give coding agents instructions for working with jj-stack:
gh skill install bos/jj-stack jj-stack
The skill source and evaluation notes are included in this repo.
Development
With uv, jj, and just installed, run just to list the development workflows. See
CONTRIBUTING.md for setup and validation instructions.
Metadata
Release files for jj-stack 0.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jj_stack-0.1.4.tar.gz | 455.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jj_stack-0.1.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 683.5 kB
Release files / jj_stack-0.1.4.tar.gz
| Download URL | jj_stack-0.1.4.tar.gz |
|---|---|
| Size | 455.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2115f553bb5db56d357421a38e07112220771d25aeb1af8c38d1f7d9e22a8999
|
|
BLAKE2b-256 checksum How to use checksums |
524f7f905e8b6f96bc83a53b1e401cedc7512f3ad3591a32816b6696b394c674
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / jj_stack-0.1.4-py3-none-any.whl
| Download URL | jj_stack-0.1.4-py3-none-any.whl |
|---|---|
| Size | 228.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
80db5142d53f0a9a902129ecb935cff0beab8020db5da9b423cd58225c4822f1
|
|
BLAKE2b-256 checksum How to use checksums |
1f074e9aab1ee9f0c6089e32a15463ac3b0bd9d4ffb5d7056223c305e2be0bcd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|