2026-07-29 ยท Joel Holsteen
Introducing GitStack
[!info] how to read this post
I bold a sentence or two per section for skimming, not for emphasis. If you are in a hurry, read only the bold text and you should come away with the shape of the whole post.
For as long as I have used Git, one pull request meant one feature branch. A person picked up a feature, wrote it, and opened a PR when it was done. The PR came out about the size of a feature, because a feature was about as much as one person could write before it was ready to ship. The mapping held because human coding speed set the size.
That mapping is breaking. Code moves much faster now, a lot of it written by coding agents, and the amount of work that lands between "start" and "ready" has grown well past what fits in one reviewable PR. The unit of work outgrew the unit of review.
Stacking is the known fix: split one branch into a sequence of small reviews that build on each other and land bottom-up. The idea is old and good. The tooling around it has stayed complicated enough that most people bounce off it, and the existing options have not kept pace with how fast code is being written now. GitStack is my attempt to make stacking something you would actually reach for.
What GitStack is
GitStack is a source forge built around stacked reviews, and the gs CLI is
meant to feel like Git with a more modern UX. If you know
git add, git commit, and git switch, you already know most of gs. It is
close to a drop-in replacement, with better UX around review and integration
built for the coding agents that write a growing share of the code.
Why big PRs get rubber-stamped
A forty-file, 2,000 line pull request gets rubber-stamped. I have done the rubber-stamping. You open the diff, see that it is too much to hold in your head, skim for anything that looks dangerous, and approve. The size defeats the review, and the review is the whole point.
Stacking keeps each slice small enough to read without giving up the single branch you work on. You commit in one linear history, and the tool tracks which commits belong to which review.
The workflow
You commit as you go in small commits, then group them into reviews. An agent with the gs skill installed can run this whole loop for you (more on that below), but it helps to know what the tool is doing. Start a branch and commit as you work:
gs switch -c add-billing
gs add internal/billing/model.go
gs commit -m "billing: subscription model"
gs add internal/billing/store.go
gs commit -m "billing: persistence layer"
Group both commits into one review, keep working, then open a second review for the next commit:
gs review create -t "billing: subscription model and storage"
gs add internal/api/billing.go
gs commit -m "billing: POST /api/billing endpoint"
gs review create -t "billing: subscription API"
A bare gs review create sweeps every commit that is not yet in a review, from
the last review up to the branch tip. The first create covers both opening
commits; the second, run after one more commit, covers just that one. The create
that reaches the branch tip pushes the branch and the stack manifest for you. The
branch now reads top to bottom as two reviews, and a reviewer opens each one on
its own instead of scrolling a single enormous diff.
Run gs stack and the branch prints itself. Here it is the next morning, after
someone has been through both slices:
> R2 wip billing: subscription API ps1 1 thread
R1 ready billing: subscription model and storage ps1 LGTM 1
next: gs submit R1
The > marks where you are standing. R1 has an approval and nothing open, so it
can land now without waiting on R2, which has a comment sitting on it. The
next: line prints on most gs commands and names the one thing to do next.
That helps a person coming back from lunch about as much as it helps an agent.
Editing in the middle of the stack
A comment lands on a lower review. Without stacking tooling you would fix it, then hand-rebase every branch stacked above it. With gs you check out the review, amend the commit, and push:
gs checkout r1
gs add internal/billing/model.go
gs amend --no-edit --resolves 01H8
gs push
01H8 is the thread id, which gs review comments r1 prints next to each open
thread. Passing it to --resolves closes that thread when you push, so you are
not going back to the web UI to click it shut.
gs amend rewrites that review's commit and rebases every review above it onto
the new version. You do not run git rebase, and you do not resolve the same
conflict twice.
Approvals survive this when they should. A review that only got rebased, with no change to its own content, keeps its approval. A review whose content actually changed loses it, because the reviewer has not seen the new version yet. The server compares patchsets per review and works out which approvals still hold, so nobody re-reads a slice that only moved.
Built for agents, too
GitStack is meant to stay usable by coding agents as much as by people. The
model helps: small review slices, clear stack state, and reviews that land
independently on one branch. gs status and gs stack print where the work
stands, and when the repo needs an action they print a next: hint, so an agent
or a person who stepped away for an hour can read the current state instead of
guessing at it.
Asking an agent to do it
Run gs skill install and gs writes out a description of its workflow that a
coding agent can pick up. You ask the agent in plain language, and it drives
the same gs verbs you would. Tell it to "split this branch into reviews",
"respond to the comments on r2", or "land the stack", and it leans on gs status
and the printed next: hints to stay oriented as it works.
If an agent writes any of your code, install the skill before you hand it a
GitStack repo. Agents learned git the same place everyone else did, and
without the skill they reach for git commit and git rebase out of habit.
Those writes go around the stack tracking, and you end up cleaning up after
a tool that was supposed to save you time. Installing it is one command:
gs skill install ~/.claude/skills/
That path is where Claude Code looks. Pass a different directory for another agent, or run it with no path to print the skill and wire it up yourself.
GitHub stays the mirror, for now
GitHub does four jobs at once. It hosts your code, it runs your CI, it tracks your issues, and it is the social network where people find your project. I am trying to fix one of those, the part where code gets written and reviewed. I would rather do that job well than ship a thin version of all four.
So you link the repo, GitStack becomes the system of record for the code and the reviews, and the GitHub copy stays in sync as a mirror. You read and approve stacks in the web console or from the CLI. Your CI keeps running on every push, whatever you run it on (if you have not tried Blacksmith, it is worth the switch). Issues and comments flow both ways, so the rest of your team, or the community around an open source project, can keep working in GitHub while you work in stacks. Your stars stay where they are.
A stack can also close the issue it fixes on either side. Declare --fixes 121
and that issue closes when the stack lands.
The end state I want is a forge you can run without GitHub in the loop at all. That is going to take a while. Until then, the mirror is what lets you use the review workflow today without giving up everything else GitHub does for you.
Closed source, for now
The gs CLI is closed source today, and there is no self-hosted build you can
run on your own hardware. I am a big fan of open source and I am open to
opening this up in the future. I have not worked out the right way to do it yet,
so for now it stays closed.
Your code is not locked in behind that. Your repository is a Git repository and
GitStack speaks the Git protocol, so git clone works against it with stock Git
and no gs installed. Reviews, comment threads, and replies are stored as refs
in that same repo rather than in a database you cannot read. If you decide
GitStack was a mistake, clone it, push it wherever you want, and you keep every
commit and every review thread.
What v0.1 means
GitStack is built on GitStack, so the daily loop gets exercised before it reaches anyone else. Every change that goes into the forge lands as a stack through the forge. The core loop is the one I use every day: commit, push, review, amend, land. Reviews land bottom-up and squash by default.
It is v0.1, and there is more to come. The core loop works today, and I am building out from there.
Try it
Install the CLI:
curl -fsSL https://gitstack.sh/install.sh | sh
Or from Homebrew:
brew install alethi-dev/tap/gs
The getting started guide walks from install to your first landed stack. Try it on your next branch. Once you are signed in, there is a feedback board in the app for bug reports and feature requests, and that feedback is worth more to me right now than almost anything else.
I will keep writing here about what I learn building and running the forge. Follow along by RSS, or find me on GitHub, LinkedIn, and Bluesky.