2026-08-03 ยท Joel Holsteen

The constraint you forgot you agreed to

[!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.

A pull request is a request to merge one branch. GitHub represents it as a pair of pointers, a head branch and a base branch, and a review is the approval to merge the first into the second. The diff view, the comment threads, and the merge button are all built on that pair.

That coupling was a product decision. GitHub shipped pull requests in 2008 for the open source workflow of the day: fork the repo, push a branch, ask the maintainer to pull it. Nothing in git defines the unit a reviewer looks at, and a branch can hold one change or fifty. GitHub chose the branch, and the choice outlived the workflow it served.

The branch got promoted

Because the PR merges a branch, the branch became the unit of review. And because nothing reaches main without review, it became the unit of work too. Teams size work to fit a branch. A refactor spotted along the way waits for a PR of its own, and scope gets trimmed to keep a diff reviewable. The branch became a shipping container with a weight limit, and work gets planned around the container. Fifteen years on, the convention passes for a law of version control, when it is only the design of one review tool.

The downstream pain

Most of the familiar pain in the PR workflow traces back to that coupling. There is no way to get feedback on part of a branch, because the PR shows the whole diff or nothing. So when work runs long, the path of least resistance is to keep committing to the same branch, and the PR grows into a two-thousand-line review that will be skimmed rather than read, by default and without anyone deciding it.

The disciplined alternative is to split the change across stacked branches, one PR per piece, each based on the branch below. Few teams sustain it, because GitHub gives the chain no support. Feedback on a lower branch has to be threaded through every branch above it, by rebase and force-push or by merging each parent down into its child, and each PR's base has to be re-pointed as the pieces land. The branches themselves have no meaning as lines of development; they exist so GitHub has something to point each PR at. The split turns one line of work into branch administration.

A review without its own branch

I am building GitStack to make it easier to ship high quality code, reviewed well, at the pace agents now write it. Removing the branch-per-review coupling is the center of that. In GitStack a review is a slice of commits on the branch where the work already lives. A manifest in the repo records which commits belong to which review, so the branch stays a branch and no refs exist for review's sake. The gs CLI is compatible with the git workflow you already have, and commits get grouped into reviews wherever the change divides naturally:

gs switch -c search-filters

gs add internal/search/filter.go
gs commit -m "search: filter model"
gs review create -t "search: filter model"

gs add internal/api/search.go
gs commit -m "search: filter params on /api/search"
gs review create -t "search: filter params on /api/search"

That is two reviews on one branch, with no refs invented for the tool's benefit. gs stack prints the state:

> R2  wip    search: filter params on /api/search  ps1
  R1  ready  search: filter model                  ps1  LGTM 1
next: gs submit R1

R1 has an approval, so it can land now, while R2 is still under review and new commits continue on the same branch. When feedback arrives on a lower review after more work has accumulated above it, the fix goes into that review directly: check out the review, amend the commit, push, and everything above it rebases automatically. The branch goes back to being a line of development, and the reviews are a layer on top of it.

What the slices change about review itself

A review sized to fit in a reader's head gets read instead of skimmed. The title states what the slice is for, the diff is small enough to hold in mind, and the comments address the change rather than its size. Reviewers rubber-stamp diffs too large to hold, and a small slice removes the reason to.

Agent reviewers get the same benefit in a stronger form, as context. An agent pointed at an enormous diff spends most of its attention reconstructing what the change is before it can judge whether the change is right. A titled slice supplies the intent along with a diff small enough to reason about, and the review it produces is more accurate for the same model.

Slices also let review run in parallel. Five reviews in a stack can be read by five reviewers, human or agent, at once, and the bottom of the stack lands the moment it is approved while the top is still under discussion. A single large PR serializes everything behind the slowest open question on any line of it.

Try it on one branch

If your branches have outgrown your PRs, try working this way for one branch and see how it reads. 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, and the launch post covers the rest of the product, including how the GitHub mirror keeps your CI and issues where they are.

You can find me on GitHub, LinkedIn, and Bluesky, and the blog has an RSS feed.