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 let jj-stack update the
matching PRs.
Quick start
Requirements
- Python 3.14 or newer
jj0.45.1 or newer- GitHub authentication
Install
Install jj-stack from PyPI with uv in an isolated tool environment (recommended):
uv tool install jj-stack
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, rerun its command with --force. If the command is
not on your shell PATH, run uv tool update-shell.
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.
Submit your first stack
Start with a linear series of local jj changes on top of trunk(). In a new repo, check
the 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; every PR targets the PR branch below it, except the
bottom PR, which targets trunk by default:
jj-stack/add-ui-... -> PR #3 (base: jj-stack/add-api-...)
jj-stack/add-api-... -> PR #2 (base: jj-stack/refactor-model-...)
jj-stack/refactor-model-... -> PR #1 (base: main)
main -> trunk
The PR branches normally stay out of your local bookmark view. When you rewrite a change,
jj-stack updates that change's existing PR branch and PR, along with the PR branches and PRs for
dependent changes.
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 mergewhen the changes at the bottom are ready. - After a queued or externally initiated merge 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.
Learn more
- Mental model
- Quick start
- Everyday workflows
- Configuration
- Writing PR descriptions
- Troubleshooting
- Tool comparison
- JSON output
- Automation and exit codes
The built-in help is the canonical flag reference:
jj-stack --help
jj-stack <command> --help
jj-stack help --all
Development
Contributor workflows live in the justfile. With uv, jj, and just installed,
run just to list the setup, formatting, focused test, verification, documentation, and release
recipes.
Coding agent integration
Install the bundled skill to teach coding agents to work with local jj stacks and refresh their
GitHub PRs safely:
gh skill install bos/jj-stack jj-stack
See the skill source. In
my evaluations with Codex and Claude Code, agents with the jj-stack skill succeeded in 11/12
scenarios versus 6/12 without it, with one critical error versus four, using 60% fewer failed
command attempts and 18% fewer tool calls. Treat that as a small pilot rather than a published
benchmark:
evals/jj-stack-skill.md
gives the evaluation design, but this repo does not include the traces behind those numbers.
(The critical error was due to Claude Haiku understanding a rule and ignoring it. I haven't
figured out how to get smaller Claude models to behave better, and I don't personally use them.)
Performance
Although jj-stack is written in Python, this does not significantly affect its speed.
The real determinants of its performance are the GitHub API and the jj command.
The GitHub API is slow; a single roundtrip takes many hundreds of milliseconds. jj-stack
reduces its impact with:
- GraphQL batch requests where possible
- concurrent use of the GitHub REST API
- periodic audits that its queries are minimal in extent
In pursuit of good performance, jj-stack also batches calls to jj and minimizes the amount
of work those calls must do.
Metadata
Release files for jj-stack 0.1.3
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.3.tar.gz | 443.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jj_stack-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 677.5 kB
Release files / jj_stack-0.1.3.tar.gz
| Download URL | jj_stack-0.1.3.tar.gz |
|---|---|
| Size | 443.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b4a8336835fb14f361ee3e74ae3078a5dff60b9d4224db4901a704ff87edae48
|
|
BLAKE2b-256 checksum How to use checksums |
33ce05e152558bb7eee67876f783598041d6cad52104ef9790bfafb2f64ce86a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","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.3-py3-none-any.whl
| Download URL | jj_stack-0.1.3-py3-none-any.whl |
|---|---|
| Size | 233.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
83e8582686c2d4d6eb1642533f3ec1450d8233a4c6fa34452792f121dc0e5fff
|
|
BLAKE2b-256 checksum How to use checksums |
9a38fa6b1b2baa91a4be4e256fe1c65e40c41a88ede1b21739a1cd9b81866aaa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","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}
|