Split View: GitHub Stacked PR 완전 가이드 — 네이티브 스택 PR이 해결한 것과 그대로 남긴 것
GitHub Stacked PR 완전 가이드 — 네이티브 스택 PR이 해결한 것과 그대로 남긴 것
- 들어가며 — 1,400줄짜리 PR을 리뷰해 달라는 요청
- 스택이 해결하는 문제 — 리뷰 단위를 의존 순서대로 자른다
- GitHub 네이티브 스택이 실제로 하는 일
- gh-stack — 명령 단위 워크플로
- 리베이스 캐스케이드 — 스택의 진짜 비용
- 기존 도구들과 무엇이 다른가
- 스택이 손해인 경우
- 마치며 — 도구가 자동화한 것은 리베이스의 실행이지 설계가 아니다
- 참고 자료
들어가며 — 1,400줄짜리 PR을 리뷰해 달라는 요청
리뷰 요청이 왔는데 변경 파일이 47개, 추가된 줄이 1,400줄입니다. 마이그레이션·리포지토리 계층·API 핸들러·프론트엔드 폼이 한 장에 다 들어 있습니다. 리뷰어가 할 수 있는 선택은 사실상 셋뿐입니다. 이틀을 통째로 비우거나, 대충 승인하거나, "쪼개 주세요"라고 답하는 것. 세 번째를 고르면 이번에는 작성자가 곤란해집니다 — git 위에서 의존 관계가 있는 변경을 여러 PR로 자르고, 리뷰 피드백이 아래쪽 조각에 들어올 때마다 위쪽 전부를 다시 리베이스해야 하기 때문입니다.
2026년 7월 30일, GitHub이 Stacked Pull Requests를 퍼블릭 프리뷰로 공개했습니다. 며칠에 걸쳐 모든 리포지토리로 순차 롤아웃되며, 웹·모바일·CLI에서 모두 동작합니다. Hacker News 스레드는 수백 개의 댓글이 달릴 만큼 반응이 컸는데, 흐름은 "드디어"와 "Phabricator·Gerrit이 10년 전에 하던 것"으로 갈렸습니다. 정확한 투표 수는 시점에 따라 달라지므로 여기서는 숫자를 인용하지 않겠습니다.
이 글은 발표 요약이 아니라 스택 워크플로 자체에 대한 안내서입니다. 스택이 실제로 해결하는 문제, GitHub 구현이 하는 일과 하지 않는 일, 그리고 스택의 진짜 비용인 리베이스 캐스케이드를 순서대로 봅니다. PR을 쪼개는 일반론은 머지되는 PR과 커밋 쓰기 편에서 다뤘으니, 여기서는 스택이라는 구조에 집중합니다.
스택이 해결하는 문제 — 리뷰 단위를 의존 순서대로 자른다
큰 변경을 작게 쪼개라는 조언은 오래됐지만, 실무에서 자주 실패합니다. 이유는 단순합니다. 의존 관계가 있는 변경은 그냥 쪼갤 수 없습니다. 스키마 마이그레이션 없이는 리포지토리 계층이 컴파일되지 않고, 리포지토리 없이는 API 핸들러가 동작하지 않습니다. 브랜치를 셋으로 나누면 두 번째 브랜치는 main이 아니라 첫 번째 브랜치를 베이스로 삼아야 하는데, 그 순간 PR 화면에는 첫 번째 브랜치의 변경까지 전부 섞여 나옵니다.
스택은 이 문제를 "베이스를 트렁크가 아니라 아래 층으로 둔다"는 한 가지 규칙으로 풉니다.
main
└── feat/schema PR #1 base: main
└── feat/repo PR #2 base: feat/schema
└── feat/api PR #3 base: feat/repo
└── feat/ui PR #4 base: feat/api
이 구조가 주는 것은 세 가지입니다.
- 리뷰 diff의 축소. PR #3을 열면 API 핸들러 변경만 보입니다. 아래 두 층은 이미 베이스에 포함돼 있으므로 diff에서 빠집니다.
- 리뷰 병렬화. DBA는 #1을, 백엔드 리뷰어는 #2와 #3을, 프론트엔드는 #4를 동시에 봅니다. 순차 대기가 사라집니다.
- 부분 착륙. #1과 #2가 승인되면 위쪽 리뷰가 끝나기 전에 먼저 병합할 수 있습니다. 리뷰 지연이 전체를 붙잡지 않습니다.
반대로 스택이 만들어 내는 것도 하나 있습니다. 아래 층이 바뀔 때마다 위쪽 전부를 다시 정렬해야 한다는 것 — 뒤에서 따로 봅니다.
GitHub 네이티브 스택이 실제로 하는 일
체인지로그와 gh-stack 저장소 기준으로, 이번 프리뷰가 서버 쪽에서 제공하는 것은 크게 넷입니다.
첫째, 스택 맵입니다. 각 PR 상단에 이 PR이 스택의 어느 층인지, 위아래에 무엇이 있는지 보여 주는 지도가 붙습니다. 리뷰어가 "이 변경이 전체에서 어디쯤인가"를 PR 설명의 수기 체크리스트가 아니라 UI에서 확인합니다. 지금까지 스택 도구들이 PR 본문에 마크다운 목록을 자동 삽입해 흉내 내던 부분이 서버 기능이 됐습니다.
둘째, 층별 diff입니다. 리뷰어가 PR을 열면 그 층의 변경만 표시됩니다. 이건 사실 베이스 브랜치가 아래 층으로 설정돼 있으면 원래 그렇게 동작하지만, 아래 층이 병합되거나 리베이스된 뒤에도 그 상태가 유지되도록 서버가 관리해 준다는 점이 다릅니다.
셋째, 두 가지 병합 방식입니다.
- 준비된 최상단 PR을 병합하면 그 아래의 미병합 층이 한 번의 작업으로 함께 착륙합니다.
- 아래쪽 층만 먼저 병합하면 위쪽 PR들이 자동으로 리베이스되고 베이스가 재조준됩니다. 지금까지 손으로 하거나 CLI 도구에 맡기던 부분입니다.
넷째, 기존 보호 규칙과의 호환입니다. 브랜치 보호, 필수 체크, 리뷰 규칙이 스택의 각 PR에 그대로 적용됩니다. 머지 큐 지원은 "몇 주에 걸쳐 점진 롤아웃"이라고만 적혀 있어, 이 글 시점에서 모든 리포지토리에서 동작한다고 단정할 수 없습니다.
여기서 분명히 해 둘 것이 있습니다. GitHub의 스택은 브랜치 단위입니다. 커밋 단위 리뷰가 아닙니다. HN 스레드에서 가장 반복된 비판이 정확히 이 지점이었습니다 — Phabricator나 Gerrit처럼 커밋 하나가 곧 리뷰 단위이고 인터디프(interdiff)를 볼 수 있는 모델을 기대했다면, 이번 프리뷰는 그것이 아닙니다. 층마다 브랜치를 만들어야 하고, 강제 푸시로 diff가 바뀌면 리뷰 코멘트가 사라지는 GitHub의 기존 동작도 그대로입니다.
gh-stack — 명령 단위 워크플로
CLI 확장이 로컬 쪽을 담당합니다. 스택 메타데이터는 .git/gh-stack에 JSON으로 저장되며 커밋되지 않습니다. 즉 스택의 순서 정보는 로컬 상태이고, 서버는 PR의 베이스 관계로 스택을 인식합니다.
# 설치 (gh 2.0 이상)
gh extension install github/gh-stack
# 1) 트렁크에서 스택 시작
git switch main && git pull --ff-only
gh stack init feat/schema
git commit -am "add nullable columns for new pricing model"
# 2) 층 쌓기 — 현재 브랜치 위에 새 브랜치를 만든다
gh stack add feat/repo
git commit -am "repository reads new columns behind a flag"
gh stack add feat/api
git commit -am "expose pricing endpoint"
# 3) 현재 스택 확인
gh stack view
# 4) 세 층을 PR로 만들고 서로 연결
gh stack submit
리뷰 피드백이 아래층에 들어왔을 때가 스택의 본론입니다.
# 아래로 두 칸 내려가 수정
gh stack down
gh stack down
git commit -am "address review: keep columns nullable for one release"
# 페치 → 캐스케이드 리베이스 → 푸시 → PR 상태 동기화를 한 번에
gh stack sync
# 리베이스만 따로 돌리고 싶다면
gh stack rebase
# 층 순서를 바꾸거나 합치기 (선형 히스토리 필요)
gh stack modify
# 준비된 지점까지 원자적으로 병합
gh stack merge
이동 명령이 따로 있다는 점이 실사용에서 생각보다 큽니다. gh stack up / down / top / bottom / trunk로 층 사이를 오가고, gh stack checkout으로 특정 PR 번호나 브랜치로 바로 갑니다. 여러 스택에 동시에 속한 브랜치를 지목하면 명령이 종료 코드 6으로 모호성을 알리므로, 스크립트에서 이 코드를 분기 조건으로 쓸 수 있습니다.
문서에 적힌 제약도 미리 알아 둘 값어치가 있습니다. gh stack push는 원자적이지 않습니다. 브랜치별로 --force-with-lease를 걸어 순차 푸시하므로, 중간에 한 브랜치가 거부되면 그 브랜치만 실패한 상태로 남고 다시 실행해야 합니다. 로컬 스택과 원격이 크게 갈라진 경우 sync는 대화형 모드에서만 해결을 물어보므로, CI에서 무인 실행할 대상이 아닙니다.
리베이스 캐스케이드 — 스택의 진짜 비용
스택의 비용은 브랜치를 여러 개 만드는 수고가 아닙니다. 아래가 움직이면 위가 전부 흔들린다는 구조 자체입니다.
높이 n짜리 스택에서 바닥 층을 한 번 수정하면, 그 위 n-1개 브랜치를 순서대로 리베이스해야 합니다. 이때 같은 충돌이 층마다 반복해서 나타나는 일이 흔합니다. 바닥에서 함수 시그니처를 바꿨다면 그 함수를 쓰는 모든 상위 층에서 같은 모양의 충돌을 다시 풀어야 하니까요. 도구가 자동화해 주는 것은 리베이스의 실행이지 충돌의 판단이 아닙니다.
이 비용을 줄이는 수단은 도구와 무관하게 셋입니다.
# 1) 같은 충돌을 두 번 풀지 않기 — rerere는 해결 결과를 기록해 재사용한다
git config --global rerere.enabled true
# 2) 리베이스가 중간 브랜치 참조를 함께 옮기게 하기 (Git 2.38+)
git config --global rebase.updateRefs true
# 3) 베이스가 바뀐 경우의 정확한 이식 — 옮길 구간을 명시한다
git rebase --onto origin/main <이전-베이스-커밋> feat/api
rerere는 스택 워크플로에서 체감 차이가 가장 큰 설정입니다. 동일한 충돌 해결을 층마다 반복 입력하던 작업이 사라집니다. rebase.updateRefs는 Git 2.38에서 들어온 옵션으로, 한 번의 리베이스로 스택 전체의 브랜치 포인터를 함께 전진시킵니다 — 사실상 순수 git만으로 하는 최소한의 스택 지원입니다.
그리고 캐스케이드가 발생할 때마다 위쪽 PR의 CI가 전부 다시 돕니다. 높이 5짜리 스택에서 바닥을 세 번 고치면 CI 실행은 15회입니다. 여기에 머지 큐가 붙으면 큐 진입 시 한 번 더 돌 수 있습니다. 스택이 리뷰 시간은 줄이지만 CI 비용은 늘린다는 사실은 도입 전에 계산해 둘 항목입니다.
마지막으로 협업 측면의 함정 하나. 스택 브랜치는 강제 푸시가 일상입니다. 다른 사람이 같은 브랜치에 커밋을 얹었다면 그 커밋이 날아갈 수 있습니다. --force-with-lease가 기본 방어선이지만 만능은 아니므로, 스택 브랜치는 단일 작성자 소유라는 규칙을 팀에서 명시하는 편이 안전합니다.
기존 도구들과 무엇이 다른가
스택은 GitHub이 처음 만든 개념이 아닙니다. 오히려 이 영역은 도구가 과잉 공급된 쪽에 가까웠습니다.
| 도구 | 리뷰 단위 | 상태를 어디에 두는가 | 캐스케이드 자동화 | 서버 측 통합 | 비용 |
|---|---|---|---|---|---|
| GitHub 네이티브 + gh-stack | 브랜치 | 로컬 .git/gh-stack + 서버의 PR 베이스 관계 | sync·rebase 명령, 하위 병합 시 서버가 자동 재조준 | 스택 맵, 층별 diff, 원자적 병합 | 프리뷰 기간 무료 |
| Graphite | 브랜치 | 자체 서비스 + 로컬 CLI | gt sync·restack로 자동 | 자체 웹 리뷰 UI, 머지 큐 | 유료 SaaS(무료 티어 있음) |
| ghstack | 커밋 | 커밋 메시지에 심은 식별자 | 커밋 스택을 통째로 다시 밀어 넣는 방식 | 없음(PR만 생성) | 오픈소스 |
| git-branchless | 커밋 | 로컬 이벤트 로그 | git move, git sync로 부분 트리 이동 | 없음 | 오픈소스 |
| Sapling | 커밋 | 자체 VCS 상태 | 스택 편집이 기본 동작 | GitHub PR 생성 지원 | 오픈소스 |
| 순수 git | 브랜치 | 없음 | rebase.updateRefs만 | 없음 | 무료 |
핵심 차이는 두 줄로 정리됩니다. 커밋 단위 도구(ghstack, git-branchless, Sapling, 그리고 정신적 후계인 jj)는 "커밋 하나가 곧 리뷰 하나"라는 모델을 취하고 브랜치를 사용자에게서 숨깁니다. 브랜치 단위 도구(Graphite, GitHub 네이티브)는 GitHub의 PR 모델을 그대로 두고 그 위에 스택 개념을 얹습니다.
GitHub이 브랜치 단위를 고른 것은 기존 보호 규칙·필수 체크·CODEOWNERS와 충돌 없이 얹을 수 있는 유일한 선택이었기 때문으로 보입니다. 대신 커밋 단위 리뷰의 장점 — 인터디프, 커밋 히스토리 보존, 재작성 후에도 살아남는 리뷰 코멘트 — 은 얻지 못했습니다. HN 스레드의 비판은 대부분 이 교환에 대한 것이었습니다.
이미 Graphite를 쓰고 있다면 당장 옮겨야 할 이유는 크지 않습니다. 자동 리베이스와 스택 시각화는 양쪽 다 하고, Graphite 쪽이 머지 큐와 리뷰 UI에서 더 성숙합니다. 반대로 아직 아무 도구도 없이 순수 git으로 버티고 있었다면, 네이티브 스택은 도입 마찰이 가장 낮은 선택지입니다 — 새 계정도, 새 웹 UI도, 새 권한 승인도 필요 없습니다.
스택이 손해인 경우
스택은 공짜가 아니고, 모든 팀에 이득도 아닙니다. 다음 경우에는 쓰지 않는 편이 낫습니다.
변경이 실제로는 의존적이지 않을 때. 서로 독립적인 세 가지 수정이라면 그냥 PR 세 장을 각각 main 위에 만들면 됩니다. 스택으로 묶는 순간 존재하지 않던 순서 의존이 생기고, 바닥 PR이 리뷰에서 막히면 나머지가 함께 발이 묶입니다.
층의 높이가 3을 훌쩍 넘을 때. 캐스케이드 비용은 높이에 비례하고 CI 비용도 그렇습니다. 높이 8짜리 스택은 대개 "쪼개기를 잘한 것"이 아니라 "설계 단계에서 잘랐어야 할 것을 리뷰 단계에서 자르고 있는 것"입니다. 기능 플래그 뒤에 넣고 main에 직접 착륙시키는 편이 나은 경우가 많습니다.
리뷰어가 한 명이고 여유가 있을 때. 스택의 이득 중 큰 몫은 병렬 리뷰입니다. 어차피 한 사람이 순서대로 볼 것이라면 층을 나눠도 총 리뷰 시간은 줄지 않고, 작성자의 리베이스 부담만 늘어납니다.
브랜치를 여러 명이 공유할 때. 앞서 적은 대로 스택은 강제 푸시를 전제합니다. 페어 작업이나 여러 명이 한 브랜치에 커밋하는 문화라면 스택 대신 롱리빙 피처 브랜치와 주기적 머지가 안전합니다.
스쿼시 병합만 쓰는데 커밋 히스토리가 중요한 프로젝트일 때. GitHub 스택은 각 층을 개별 PR로 병합하므로 층 단위 히스토리는 남지만, 층 안의 커밋은 스쿼시 정책에 따라 사라집니다. 커밋 단위 아카이빙이 필요하다면 Sapling이나 jj 계열을 보는 편이 맞습니다.
정리하면 스택이 잘 듣는 조건은 좁습니다 — 의존 순서가 분명하고, 층이 2개에서 4개이며, 리뷰어가 여럿이고, 각 층이 단독으로도 배포 가능한 변경. 이 조건에서 벗어날수록 이득보다 리베이스 비용이 커집니다.
마치며 — 도구가 자동화한 것은 리베이스의 실행이지 설계가 아니다
GitHub 네이티브 스택은 지금까지 서드파티 도구가 흉내 내던 것들 — 스택 맵, 층별 diff, 하위 병합 후 자동 재조준, 원자적 병합 — 을 서버 기능으로 올렸습니다. 별도 서비스 가입 없이 쓸 수 있다는 점만으로도 도입 장벽이 크게 내려갔습니다.
- 지금 확인할 것:
gh extension install github/gh-stack으로 설치하고 다음 큰 변경 한 건에만 높이 3짜리 스택을 시도해 보세요. 머지 큐 연동은 아직 롤아웃 중이라 팀 리포지토리에서 실제 동작을 먼저 확인해야 합니다. - 반드시 켤 것:
rerere.enabled와rebase.updateRefs. 도구를 무엇으로 쓰든 캐스케이드 비용을 가장 크게 줄여 줍니다. - 기대하지 말 것: 커밋 단위 리뷰와 인터디프. 이번 프리뷰는 브랜치 단위 모델이고, 그 위에서 Phabricator·Gerrit의 리뷰 경험을 재현하지는 않습니다.
스택은 큰 변경을 작게 만들어 주지 않습니다. 이미 작게 자를 수 있는 변경을 자를 때 드는 마찰을 줄여 줄 뿐입니다. 잘라야 할 지점을 찾는 일은 여전히 설계의 몫입니다.
참고 자료
- Stacked pull requests are now in public preview — GitHub Changelog (2026-07-30)
- github/gh-stack — CLI 확장 저장소와 명령 레퍼런스
- GitHub Community Discussion #201439 — 스택 PR 프리뷰 공지 스레드
- Hacker News — GitHub Stacked PRs 토론
- Graphite — Stacked diffs 가이드
- ezyang/ghstack — 커밋 단위 스택 도구
- arxanas/git-branchless — 로컬 이벤트 로그 기반 스택 편집
- Sapling SCM — 스택을 기본으로 삼는 VCS
- git-rebase 문서 — update-refs와 onto 옵션
- git-rerere 문서 — 충돌 해결 재사용
- 머지되는 PR과 커밋 쓰기 (관련 글)
- 코드 리뷰 커뮤니케이션 (관련 글)
The Complete Guide to GitHub Stacked PRs — What Native Stacking Solved, and What It Left Standing
- Introduction — a Request to Review a 1,400-Line PR
- What Stacking Solves — Cutting Review Units Along Dependency Order
- What GitHub's Native Stack Actually Does
- gh-stack — a Command-Level Workflow
- The Rebase Cascade — Stacking's Real Cost
- How It Differs From Existing Tools
- When Stacking Actually Hurts
- Conclusion — the Tool Automated the Execution of a Rebase, Not the Design
- References
Introduction — a Request to Review a 1,400-Line PR
A review request lands with 47 files changed and 1,400 lines added. A migration, a repository layer, API handlers, and a frontend form are all sitting in one PR. A reviewer effectively has three options: clear out an entire day, rubber-stamp it, or reply "please split this up." Pick the third, and now the author is stuck — cutting a set of dependent changes across git into multiple PRs means rebasing everything above whenever review feedback lands on a piece underneath.
On July 30, 2026, GitHub shipped Stacked Pull Requests into public preview. It's rolling out sequentially to every repository over a few days, and it works across web, mobile, and CLI. The Hacker News thread drew hundreds of comments, split between "finally" and "this is what Phabricator and Gerrit were doing ten years ago." Exact vote counts shift over time, so I won't quote a number here.
This post isn't a summary of the announcement — it's a field guide to the stacking workflow itself. It walks through, in order, the problem stacking actually solves, what GitHub's implementation does and doesn't do, and the rebase cascade that's stacking's real cost. General advice on splitting PRs is covered in Writing PRs and Commits That Get Merged; here the focus stays on stacking as a structure.
What Stacking Solves — Cutting Review Units Along Dependency Order
The advice to split a large change into small ones is old, but it fails often in practice. The reason is simple: you can't just split a change that has dependencies. The repository layer won't compile without the schema migration, and the API handler won't work without the repository. Split into three branches, and the second branch has to base off the first branch instead of main — and the moment it does, the PR view mixes in every change from the first branch too.
Stacking solves this with one rule: base each layer off the layer below, not off trunk.
main
└── feat/schema PR #1 base: main
└── feat/repo PR #2 base: feat/schema
└── feat/api PR #3 base: feat/repo
└── feat/ui PR #4 base: feat/api
This structure gives you three things.
- A smaller review diff. Open PR #3 and you see only the API handler changes. The two layers below are already part of the base, so they drop out of the diff.
- Parallel review. A DBA looks at #1, a backend reviewer looks at #2 and #3, and a frontend reviewer looks at #4, all at the same time. Sequential waiting disappears.
- Partial landing. Once #1 and #2 are approved, they can merge before review on the layers above finishes. Review delays don't hold up the whole stack.
Stacking also creates one thing in return: every time a lower layer changes, everything above it has to be realigned. That's covered separately below.
What GitHub's Native Stack Actually Does
Per the changelog and the gh-stack repository, what this preview provides on the server side breaks down into four things.
First, a stack map. A map showing which layer of the stack a given PR sits at, and what's above and below it, appears at the top of every PR. A reviewer checks "where does this change sit in the whole picture" from the UI rather than a hand-maintained checklist in the PR description. What stacking tools used to fake by auto-inserting a markdown list into the PR body has become a server-side feature.
Second, per-layer diffs. When a reviewer opens a PR, only that layer's changes show. This actually already happens naturally when the base branch is set to the layer below — what's different is that the server keeps that state consistent even after a lower layer merges or gets rebased.
Third, two merge modes.
- Merging the top, ready PR lands every unmerged layer beneath it as a single operation.
- Merging only a lower layer first causes the PRs above it to be automatically rebased and re-based on the new target. This used to be done by hand or offloaded to a CLI tool.
Fourth, compatibility with existing protection rules. Branch protection, required checks, and review rules all still apply to every PR in the stack as-is. Merge queue support is described only as "rolling out gradually over the coming weeks," so as of this writing you can't assume it works in every repository yet.
There's something worth stating plainly here: GitHub's stacking is branch-level. It is not commit-level review. The single most repeated criticism in the HN thread was exactly this point — if you were expecting a model like Phabricator's or Gerrit's, where a single commit is the review unit and you can view an interdiff, this preview isn't that. You still have to create a branch per layer, and GitHub's existing behavior — where review comments vanish when a force push changes the diff — is unchanged.
gh-stack — a Command-Level Workflow
The CLI extension handles the local side. Stack metadata is stored as JSON at .git/gh-stack and isn't committed. In other words, the stack's ordering information is local state, and the server recognizes the stack through PR base relationships.
# Install (gh 2.0 or later)
gh extension install github/gh-stack
# 1) Start the stack from trunk
git switch main && git pull --ff-only
gh stack init feat/schema
git commit -am "add nullable columns for new pricing model"
# 2) Stack a layer — create a new branch on top of the current one
gh stack add feat/repo
git commit -am "repository reads new columns behind a flag"
gh stack add feat/api
git commit -am "expose pricing endpoint"
# 3) Check the current stack
gh stack view
# 4) Turn all three layers into PRs and link them together
gh stack submit
The real substance of stacking shows up when review feedback lands on a lower layer.
# Go down two levels and fix
gh stack down
gh stack down
git commit -am "address review: keep columns nullable for one release"
# fetch -> cascading rebase -> push -> sync PR state, all in one shot
gh stack sync
# to run just the rebase on its own
gh stack rebase
# reorder or combine layers (requires linear history)
gh stack modify
# atomically merge up to the point that's ready
gh stack merge
Having dedicated movement commands turns out to matter more than expected in practice. gh stack up / down / top / bottom / trunk move you between layers, and gh stack checkout jumps straight to a specific PR number or branch. Point at a branch that belongs to multiple stacks at once, and the command signals ambiguity with exit code 6, which you can branch on in a script.
A few constraints in the documentation are worth knowing ahead of time. gh stack push is not atomic. It pushes branch by branch with --force-with-lease, so if one branch gets rejected midway through, only that branch is left in a failed state and needs to be re-run. When the local stack and the remote have diverged significantly, sync only asks how to resolve it in interactive mode — so it's not something you run unattended in CI.
The Rebase Cascade — Stacking's Real Cost
Stacking's cost isn't the effort of creating multiple branches. It's the structural fact that when the bottom moves, everything above it shakes.
In a stack of height n, modifying the bottom layer once means you have to rebase the n-1 branches above it, in order. It's common for the same conflict to reappear at every layer — change a function signature at the bottom, and every layer above that uses that function has to resolve the same shape of conflict all over again. What the tooling automates is the execution of the rebase, not the judgment of the conflict.
There are three ways to cut this cost, and they're tool-independent.
# 1) Don't resolve the same conflict twice — rerere records resolutions and replays them
git config --global rerere.enabled true
# 2) Have rebase move intermediate branch refs along with it (Git 2.38+)
git config --global rebase.updateRefs true
# 3) Precise transplantation when the base has changed — name the range explicitly
git rebase --onto origin/main <previous-base-commit> feat/api
rerere is the single setting with the biggest felt difference in a stacking workflow. It eliminates re-typing the same conflict resolution at every layer. rebase.updateRefs, introduced in Git 2.38, advances every branch pointer in the stack together with a single rebase — effectively minimal stack support with plain git alone.
And every time a cascade fires, CI reruns on every PR above it. Fix the bottom of a height-5 stack three times, and CI runs 15 times. Add a merge queue on top of that, and it can run once more when entering the queue. The fact that stacking cuts review time but raises CI cost is worth calculating before you adopt it.
One last collaboration trap. Stacked branches force-push routinely. If someone else added a commit onto the same branch, that commit can vanish. --force-with-lease is the default line of defense, but it's not foolproof, so it's safer for a team to explicitly declare that a stacked branch is owned by a single author.
How It Differs From Existing Tools
Stacking isn't a concept GitHub invented. If anything, this space had an oversupply of tooling already.
| Tool | Review unit | Where state lives | Cascade automation | Server-side integration | Cost |
|---|---|---|---|---|---|
| GitHub native + gh-stack | Branch | Local .git/gh-stack + PR base relationships on the server | sync/rebase commands; server auto-realigns on a lower merge | Stack map, per-layer diffs, atomic merge | Free during preview |
| Graphite | Branch | Its own service + local CLI | Automatic via gt sync/restack | Own web review UI, merge queue | Paid SaaS (free tier available) |
| ghstack | Commit | An identifier embedded in the commit message | Pushes the whole commit stack back in as a unit | None (only creates PRs) | Open source |
| git-branchless | Commit | A local event log | Moves subtrees via git move, git sync | None | Open source |
| Sapling | Commit | Its own VCS state | Stack editing is the default behavior | Supports creating GitHub PRs | Open source |
| Plain git | Branch | None | Only rebase.updateRefs | None | Free |
The core difference reduces to two lines. Commit-level tools (ghstack, git-branchless, Sapling, and jj as their spiritual successor) take the model where one commit equals one review, and hide branches from the user. Branch-level tools (Graphite, GitHub native) leave GitHub's PR model exactly as it is and layer the stacking concept on top.
GitHub choosing branch-level appears to be the only option that could layer on without conflicting with existing protection rules, required checks, and CODEOWNERS. In exchange, it doesn't get the advantages of commit-level review — interdiffs, preserved commit history, review comments that survive a rewrite. Most of the HN thread's criticism was about exactly this trade-off.
If you're already on Graphite, there's not much reason to switch right now. Both do automatic rebasing and stack visualization, and Graphite is more mature on merge queue and review UI. Conversely, if you've been getting by with plain git and no tooling at all, native stacking is the lowest-friction option to adopt — no new account, no new web UI, no new permission approval required.
When Stacking Actually Hurts
Stacking isn't free, and it isn't a win for every team. In the following cases, you're better off not using it.
When the changes aren't actually dependent. If you have three genuinely independent fixes, just make three PRs, each directly off main. Bundle them into a stack and you create an ordering dependency that never existed — if the bottom PR stalls in review, everything else gets stuck along with it.
When the stack's height goes well past 3. Cascade cost scales with height, and so does CI cost. A height-8 stack is usually not "a change that got split well" — it's "something that should have been cut apart at the design stage, and is instead being cut apart at review time." It's often better to put it behind a feature flag and land it directly on main.
When there's a single reviewer with plenty of slack. A big share of stacking's benefit is parallel review. If one person is going to look at everything in sequence regardless, splitting into layers doesn't reduce total review time — it just adds to the author's rebase burden.
When a branch is shared by multiple people. As noted above, stacking assumes force-pushing. In a pairing culture, or one where several people commit to the same branch, a long-lived feature branch with periodic merges is safer than stacking.
When you squash-merge exclusively but commit history matters to the project. GitHub's stacking merges each layer as a separate PR, so layer-level history survives, but commits within a layer disappear under a squash policy. If you need commit-level archiving, look at Sapling or the jj family instead.
To sum up, the conditions where stacking works well are narrow — dependency order is clear, the stack is 2 to 4 layers deep, there are multiple reviewers, and each layer is independently deployable on its own. Move away from these conditions and the rebase cost outgrows the benefit.
Conclusion — the Tool Automated the Execution of a Rebase, Not the Design
GitHub's native stacking took things third-party tools used to imitate — the stack map, per-layer diffs, automatic realignment after a lower-layer merge, atomic merges — and turned them into server features. The fact that you can use it with no separate service signup alone lowers the barrier to adoption substantially.
- Check right now: install with
gh extension install github/gh-stackand try a height-3 stack on your next big change. Merge queue integration is still rolling out, so confirm actual behavior in your team's repository first. - Definitely turn on:
rerere.enabledandrebase.updateRefs. Whatever tool you use, these cut the cascade cost the most. - Don't expect: commit-level review or interdiffs. This preview is a branch-level model, and it doesn't reproduce the Phabricator/Gerrit review experience on top of it.
Stacking doesn't make a large change small. It only reduces the friction of splitting a change that could already be split cleanly. Finding the right place to cut is still the job of design.
References
- Stacked pull requests are now in public preview — GitHub Changelog (2026-07-30)
- github/gh-stack — CLI extension repository and command reference
- GitHub Community Discussion #201439 — the stacked PR preview announcement thread
- Hacker News — GitHub Stacked PRs discussion
- Graphite — Stacked diffs guide
- ezyang/ghstack — a commit-level stacking tool
- arxanas/git-branchless — stack editing based on a local event log
- Sapling SCM — a VCS with stacking as the default
- git-rebase documentation — the update-refs and onto options
- git-rerere documentation — reusing conflict resolutions
- Writing PRs and Commits That Get Merged (related post)
- Code Review Communication (related post)