.agents/skills/gh-stack/SKILL.md
gh stack is a GitHub CLI extension for managing stacked branches and pull requests. A stack is an ordered list of branches where each branch builds on the one below it, rooted on a trunk branch (typically the repo's default branch). Each branch maps to one PR whose base is the branch below it, so reviewers see only the diff for that layer.
main (trunk)
└── auth-layer → PR #1 (base: main) - bottom (closest to trunk)
└── api-endpoints → PR #2 (base: auth-layer)
└── frontend → PR #3 (base: api-endpoints) - top (furthest from trunk)
The bottom of the stack is the branch closest to the trunk, and the top is the branch furthest from the trunk. Each branch inherits from the one below it. Navigation commands (up, down, top, bottom) follow this model: up moves away from trunk, down moves toward it.
Use this skill when the user wants to:
The GitHub CLI (gh) v2.0+ must be installed and authenticated. Install the extension with:
gh extension install github/gh-stack
Before using gh stack, configure git to prevent interactive prompts:
git config rerere.enabled true # remember conflict resolutions (skips prompt on init)
git config remote.pushDefault origin # if multiple remotes exist (skips remote picker)
All gh stack commands must be run non-interactively. Every command invocation must include the flags and positional arguments needed to avoid prompts, TUIs, and interactive menus. If a command would prompt for input, it will hang indefinitely.
init, add, and checkout. Running these commands without arguments triggers interactive prompts. Branch names are used exactly as given — a name is never prefixed or transformed, so gh stack add refactor/foo creates a branch named refactor/foo.--auto with gh stack submit to auto-generate PR titles. Without --auto, submit prompts for a title for each new PR.--json with gh stack view. Without --json, the command launches an interactive TUI that cannot be operated by agents. There is no other appropriate flag — always pass --json.--remote <name> when multiple remotes are configured, or pre-configure git config remote.pushDefault origin. Without this, push, submit, sync, link, and checkout trigger an interactive remote picker.gh stack init.git add and git commit for staging and committing. This gives you full control over which changes go into each branch. The -Am shortcut is available but should not be the default approach—stacked PRs are most effective when each branch contains a deliberate, logical set of changes.gh stack down, gh stack checkout, or gh stack bottom), make and commit the changes there, run gh stack rebase --upstack, then navigate back up to continue.gh stack link for external tool workflows. When branches are managed by an external tool (jj, Sapling, etc.), use gh stack link branch-a branch-b. link does not rely on local tracking state and is intended for API-driven PR and stack management. Provide at least two branches/PRs to create or update a stack, or a stack number followed by the new branches/PRs to append them to the top of an existing stack (e.g. gh stack link 7 branch-c).Never do any of the following — each triggers an interactive prompt or TUI that will hang:
gh stack view or gh stack view --short — always use gh stack view --jsongh stack submit without --auto — always use gh stack submit --autogh stack init without branch arguments — always provide branch namesgh stack add without a branch name — always provide a branch namegh stack checkout without an argument — always provide a PR number or branch namegh stack checkout <pr-number> when a different local stack already exists on those branches — this triggers an unbypassable conflict resolution prompt; use gh stack unstack first to remove the local stack, then retry the checkoutEach branch in a stack should represent a discrete, logical unit of work that can be reviewed independently. The changes within a branch should be cohesive—they belong together and make sense as a single PR.
Stacked branches form a dependency chain: each branch builds on the one below it. This means foundational changes must go in lower (earlier) branches, and code that depends on them goes in higher (later) branches.
Plan your layers before writing code. For example, a full-stack feature might be structured like this (use branch names relevant to your actual task, not these generic ones):
main (trunk)
└── data-models ← shared types, database schema
└── api-endpoints ← API routes that use the models
└── frontend-ui ← UI components that call the APIs
└── integration ← tests that exercise the full stack
This is illustrative — choose branch names and layer boundaries that reflect the specific work you're doing. The key principle is: if code in one layer depends on code in another, the dependency must be in the same branch or a lower one.
Choose a clear, descriptive branch name for each layer that reflects the concern it contains (e.g., auth, api-routes, frontend). Branch names are used exactly as you provide them to init and add — nothing is prepended or transformed. Slashes are allowed and are treated as part of the name (e.g., gh stack add refactor/foo creates a branch named refactor/foo).
The main reason to use git add and git commit directly is to control which changes go into which branch. When you have multiple files in your working tree, you can stage a subset for the current branch, commit them, then create a new branch and stage the rest there:
# You're on data-models with several new files in your working tree.
# Stage only the model files for this branch:
git add internal/models/user.go internal/models/session.go
git commit -m "Add user and session models"
git add db/migrations/001_create_users.sql
git commit -m "Add user table migration"
# Now create a new branch for the API layer and stage the API files there:
gh stack add api-routes # created & switched to the api-routes branch
git add internal/api/routes.go internal/api/handlers.go
git commit -m "Add user API routes"
This keeps each branch focused on one concern. Multiple commits per branch are fine — the key is that all commits in a branch relate to the same logical concern, and changes that belong to a different concern go in a different branch.
Create a new branch (gh stack add) when you're starting a different concern that depends on what you've built so far. Signs it's time for a new branch:
Think of a stack from the reviewer's perspective: the stack of PRs should tell a cohesive story about a feature or project. A reviewer should be able to read the PRs in sequence and understand the progression of changes, with each PR being a small, logical piece of the whole.
When to use a single stack: All the branches are part of the same feature, project, or closely related effort. Even if the work spans multiple concerns (models, API, frontend), they're all building toward the same goal.
When to create a separate stack: The work is unrelated to your current stack — a different feature, a bug fix in an unrelated area, or an independent refactor. Don't mix unrelated work into a single stack just because you happen to be working on both. Start a new stack with gh stack init or switch to an existing stack with gh stack checkout for each distinct effort.
Small, incidental fixes (e.g., fixing a typo you noticed) can go in the current stack if they're trivial. But if a change grows into its own project, it deserves its own stack.
| Task | Command |
|---|---|
| Create a stack | gh stack init auth |
| Create a stack of multiple branches | gh stack init auth api frontend |
| Adopt existing branches | gh stack init existing-branch-a existing-branch-b |
| Set custom trunk | gh stack init --base develop branch-a |
| Add a branch to stack | gh stack add api-routes |
| Add branch + stage all + commit | gh stack add -Am "message" api-routes |
| Push branches to remote | gh stack push |
| Push to specific remote | gh stack push --remote origin |
| Push branches + create draft PRs | gh stack submit --auto |
| Create PRs as ready for review | gh stack submit --auto --open |
| Sync (fetch, rebase, push) | gh stack sync |
| Sync with specific remote | gh stack sync --remote origin |
| Sync and prune merged branches | gh stack sync --prune |
| Rebase entire stack | gh stack rebase |
| Rebase upstack only | gh stack rebase --upstack |
| Rebase without trunk | gh stack rebase --no-trunk |
| Continue after conflict | gh stack rebase --continue |
| Abort rebase | gh stack rebase --abort |
| View stack details (JSON) | gh stack view --json |
| Switch branches up/down in stack | gh stack up [n] / gh stack down [n] |
| Switch to top/bottom branch | gh stack top / gh stack bottom |
| Check out by stack number | gh stack checkout 7 |
| Check out by PR | gh stack checkout 42 |
| Check out by branch (local only) | gh stack checkout feature-auth |
| Tear down the current stack to restructure it | gh stack unstack |
| Tear down a specific stack by number | gh stack unstack 7 |
# 1. Initialize a stack with the first branch
gh stack init auth
# → creates auth and checks it out
# 2. Write code for the first layer (auth)
cat > auth.go << 'EOF'
package auth
func Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// verify token
next.ServeHTTP(w, r)
})
}
EOF
# 3. Stage and commit using standard git commands
git add auth.go
git commit -m "Add auth middleware"
# You can make multiple commits on the same branch
cat > auth_test.go << 'EOF'
package auth
func TestMiddleware(t *testing.T) {
// test auth middleware
}
EOF
git add auth_test.go
git commit -m "Add auth middleware tests"
# 4. When you're ready for a new concern, add the next branch
gh stack add api-routes
# → creates api-routes
# 5. Write code for the API layer
cat > api.go << 'EOF'
package api
func RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("/users", handleUsers)
}
EOF
git add api.go
git commit -m "Add API routes"
# 6. Add a third layer for frontend
gh stack add frontend
# → creates frontend
cat > frontend.go << 'EOF'
package frontend
func RenderDashboard(w http.ResponseWriter) {
// calls the API endpoints from the layer below
}
EOF
git add frontend.go
git commit -m "Add frontend dashboard"
# ── Stack complete: auth → api-routes → frontend ──
# 7. Push everything and create PRs (drafts by default)
gh stack submit --auto
# 8. Verify the stack
gh stack view --json
Shortcut: If you prefer a faster flow,
gh stack add -Am "message" branch-namecombines staging, committing, and branch creation into one command. This is useful for single-commit layers but bypasses deliberate staging.
This is a critical workflow for agents. When you're working on a higher layer and realize you need to change something in a lower layer (e.g., you're building frontend components but need to add an API endpoint), navigate down to the correct branch, make the change there, and rebase.
# You're on frontend but need to add an API endpoint
# 1. Navigate to the API branch
gh stack down
# or: gh stack checkout api-routes
# 2. Make the change where it belongs
cat > users_api.go << 'EOF'
package api
func handleGetUser(w http.ResponseWriter, r *http.Request) {
// new endpoint the frontend needs
}
EOF
git add users_api.go
git commit -m "Add get-user endpoint"
# 3. Rebase everything above to pick up the change
gh stack rebase --upstack
# 4. Navigate back to where you were working
gh stack top
# or: gh stack checkout frontend
# 5. Continue working — the API changes are now available
Why this matters: If you make API changes on the frontend branch, those changes will end up in the wrong PR. The API PR won't include them, and the frontend PR will have unrelated API diffs mixed in. Always put changes in the branch where they logically belong.
When you need to revisit a branch after the initial creation (e.g., responding to review feedback):
# 1. Navigate to the branch that needs changes
gh stack bottom
# or: gh stack checkout auth
# or: gh stack checkout 42 (by PR number)
# 2. Make changes and commit
cat > auth.go << 'EOF'
package auth
// updated implementation
EOF
git add auth.go
git commit -m "Fix auth token validation"
# 3. Rebase everything above this branch
gh stack rebase --upstack
# 4. Push the updated stack
gh stack push
# Single command: fetch, rebase, push, sync PR and stack state
gh stack sync
# Sync and automatically clean up local branches for merged PRs
gh stack sync --prune
Note for agents: In non-interactive environments, the prune prompt is not shown. Use
--pruneexplicitly to delete local branches for merged PRs.
Note for agents:
syncalso mirrors the stack on GitHub locally. If PRs were added to the stack on github.com, their branches are pulled down and appended to the local stack automatically. If the local and remote stacks have diverged (you changed the local stack while the remote stack changed differently), sync can only prompt to resolve it in an interactive terminal — in non-interactive environments it aborts the sync (nothing is pushed or updated) and exits successfully withℹ Sync aborted. Resolve a divergence by unstacking and recreating the stack.
When a PR is squash-merged on GitHub, the original branch's commits no longer exist in the trunk history. gh stack detects this automatically and uses git rebase --onto to correctly replay remaining commits.
# After PR #1 (auth) is squash-merged on GitHub:
gh stack sync
# → fetches latest, detects the merge, fast-forwards trunk
# → rebases api-routes onto updated trunk (skips merged branch)
# → rebases frontend onto api-routes
# → pushes updated branches
# → reports: "Merged: #1"
# Verify the result
gh stack view --json
# → auth shows "isMerged": true, "state": "MERGED"
# → api-routes and frontend show updated heads
If sync hits a conflict during this process, it restores all branches to their pre-rebase state and exits with code 3. See Handle rebase conflicts for the resolution workflow.
# 1. Start the rebase
gh stack rebase
# 2. If exit code 3 (conflict):
# - Parse stderr for conflicted file paths
# - Read those files to find <<<<<<< / ======= / >>>>>>> markers
# - Edit files to resolve conflicts
# - Stage resolved files:
git add path/to/resolved-file.go
# 3. Continue the rebase
gh stack rebase --continue
# 4. If another conflict occurs, repeat steps 2-3
# 5. If unable to resolve, abort to restore everything
gh stack rebase --abort
--json output# Get stack state as JSON
output=$(gh stack view --json)
# Check if any branch needs a rebase, and rebase if so
needs_rebase=$(echo "$output" | jq '[.branches[] | select(.needsRebase == true)] | length')
if [ "$needs_rebase" -gt 0 ]; then
echo "Branches need rebase, rebasing stack..."
gh stack rebase
fi
# Get all open PR URLs
echo "$output" | jq -r '.branches[] | select(.pr.state == "OPEN") | .pr.url'
# Find merged branches
echo "$output" | jq -r '.branches[] | select(.isMerged == true) | .name'
# Get the current branch
echo "$output" | jq -r '.currentBranch'
# Check if the stack is fully merged (all branches merged)
echo "$output" | jq '[.branches[] | .isMerged] | all'
Use unstack to tear down the stack, make structural changes, then re-init:
# 1. Remove the stack (locally and on GitHub)
gh stack unstack
# 2. Make structural changes — e.g. delete a branch, reorder, rename
git branch -m old-branch-1 new-branch-1
# 3. Re-create the stack with the new structure
gh stack init --base main new-branch-1 new-branch-2 new-branch-3
gh stack initCreates a new stack. Always provide at least one branch name as a positional argument — running without branch arguments triggers interactive prompts that agents cannot use.
gh stack init [flags] <branches...>
# Create a stack with a new branch
gh stack init auth
# → creates auth and checks it out
# Create a stack with new branches
gh stack init branch-a branch-b branch-c
# Use a different trunk branch
gh stack init --base develop branch-a branch-b
# Adopt existing branches into a stack (handled automatically if the branches exist)
gh stack init branch-a branch-b branch-c
| Flag | Description |
|---|---|
-b, --base <branch> | Trunk branch (defaults to the repo's default branch) |
Behavior:
git rerere so conflict resolutions are remembered across rebases. On first run in a repo, this may trigger a confirmation prompt — pre-configure with git config rerere.enabled true to avoid itgh stack addAdd a new branch on top of the current stack. Must be run while on the topmost branch (or the trunk if the stack has no branches yet). Always provide a branch name — running without one triggers an interactive prompt.
gh stack add [flags] <branch>
Recommended workflow — create the branch, then use standard git:
# Create a new branch and switch to it
gh stack add api-routes
# Write code, stage deliberately, and commit
git add internal/api/routes.go internal/api/handlers.go
git commit -m "Add user API routes"
# Make more commits on the same branch as needed
git add internal/api/middleware.go
git commit -m "Add rate limiting middleware"
Shortcut — stage, commit, and branch in one command:
# Create a new branch, stage all changes, and commit
gh stack add -Am "Add API routes" api-routes
# Create a new branch, stage tracked files only, and commit
gh stack add -um "Fix auth bug" auth-fix
| Flag | Description |
|---|---|
-m, --message <string> | Create a commit with this message |
-A, --all | Stage all changes including untracked files (requires -m) |
-u, --update | Stage tracked files only (requires -m) |
Behavior notes:
-A and -u are mutually exclusive.init), add -Am commits directly on the current branch instead of creating a new one.gh stack add refactor/foo creates a branch named refactor/foo — names are never prefixed or transformed. When -m is given without a branch name, the name is auto-generated from the commit message in date+slug format (e.g., 03-24-add_api_routes)."can only add branches on top of the stack". Use gh stack top to switch first.gh stack add branch-name without -Am, any uncommitted changes (staged or unstaged) in your working tree carry over to the new branch. This is standard git behavior — the working tree is not touched. Commit or stash changes on the current branch before running add if you want a clean starting point on the new branch.gh stack pushPush all stack branches to the remote.
gh stack push [flags]
# Push all branches
gh stack push
# Push to specific remote
gh stack push --remote upstream
| Flag | Description |
|---|---|
--remote <name> | Remote to push to (use if multiple remotes exist) |
Behavior:
--force-with-lease --atomic)gh stack submit for thatOutput (stderr):
Pushed N branches summarygh stack submitPush all stack branches and create PRs on GitHub. Always pass --auto — without it, submit prompts for a PR title for each new branch.
# Submit and auto-title new PRs (required for non-interactive use)
gh stack submit --auto
# Submit and create PRs as ready for review (not drafts)
gh stack submit --auto --open
| Flag | Description |
|---|---|
--auto | Auto-generate PR titles without prompting (required for non-interactive use) |
--open | Mark new and existing PRs as ready for review |
--remote <name> | Remote to push to (use if multiple remotes exist) |
Behavior:
--force-with-lease --atomic)submit automatically forks your unmerged branches into a new stack rooted at the trunk and creates it on GitHub, leaving the merged stack untouched.submit offers to create regular (unstacked) PRs instead. In non-interactive mode, it exits with code 9.PR title auto-generation (--auto):
Output (stderr):
Created PR #N for <branch> for each newly created PRPR #N for <branch> is up to date for existing PRsPushed and synced N branches summarygh stack linkLink PRs into a stack on GitHub without creating any local tracking state. This is the recommended approach if you are managing stacked branches with other tools (jj, Sapling, git-town) and want to simply create GitHub Stacked PRs via an API.
gh stack link [flags] <stack-number | branch-or-pr> <branch-or-pr> [...]
# Link branches into a stack (pushes, creates PRs, creates stack)
gh stack link branch-a branch-b branch-c
# Use a different base branch and mark PRs as ready for review
gh stack link --base develop --open branch-a branch-b branch-c
# Link existing PRs by number
gh stack link 10 20 30
# Add branches to an existing stack of PRs
gh stack link 42 43 feature-auth feature-ui
# Append to the top of an existing stack by its stack number
# (7 is a stack number; only the new PRs/branches are listed)
gh stack link 7 48 feature-auth
When the first argument is a stack number, the remaining arguments are appended to the top of that stack, so you don't have to re-list its current PRs. Arguments already in the stack are skipped; arguments in a different stack are rejected. A numeric first argument is treated as a stack only when it matches an existing stack — otherwise it is a PR or branch.
| Flag | Description |
|---|---|
--base <branch> | Base branch for the bottom of the stack (default: main) |
--open | Mark new and existing PRs as ready for review |
--remote <name> | Remote to push to (use if multiple remotes exist) |
Behavior:
--base, subsequent branches use the previous branch)Output (stderr):
Pushing N branches to <remote>...Found PR #N for branch <name> for branches with existing PRsCreated PR #N for <branch> (base: <base>) for newly created PRsUpdated base branch for PR #N to <base> when base branches are correctedCreated stack with N PRs or Updated stack to N PRsgh stack syncFetch, rebase, push, and sync PR state in a single command. This is the recommended command for routine synchronization.
gh stack sync [flags]
| Flag | Description |
|---|---|
--remote <name> | Remote to fetch from and push to (use if multiple remotes exist) |
--prune | Delete local branches for merged PRs |
What it does (in order):
gh stack submit for that--prune to skip the prompt. In non-interactive environments, pruning only happens when --prune is passed explicitlyOutput (stderr):
✓ Fetched latest changes from originPulling N new branches from the remote stack ... then ✓ Pulled N new branches into the stack from the remote (when the remote stack is ahead)⚠ Your local stack has diverged from the stack on GitHub (with Local: / Remote: chains) when the stacks have divergedℹ Sync aborted — no changes were made when a sync is cancelled✓ Trunk main fast-forwarded to <sha> or ✓ Trunk main is already up to date✓ Rebased <branch> onto <base> per branch (if base moved)✓ Pushed N branches✓ PR #N (<branch>) — Open per branchMerged: #N, #M for merged branches✓ Stack created on GitHub with N PRs / ✓ Stack updated on GitHub with N PRs / ✓ Linked to the existing stack on GitHub (when two or more PRs exist)✓ Pruned <branch> (merged) per pruned branch (when pruning)✓ Stack synced when the stack object on GitHub was created/updated to match local, or ✓ Branches synced when only the branches were synced (fewer than two PRs or stacked PRs unavailable)gh stack rebasePull from remote and cascade-rebase stack branches. Use this when sync reports a conflict or when you need finer control (e.g., rebase only part of the stack).
gh stack rebase [flags] [branch]
# Rebase the entire stack
gh stack rebase
# Rebase only branches from trunk to current branch
gh stack rebase --downstack
# Rebase only branches from current branch to top
gh stack rebase --upstack
# Rebase stack branches without pulling from or rebasing with trunk
gh stack rebase --no-trunk
# After resolving a conflict: stage files with `git add`, then:
gh stack rebase --continue
# Abort and restore all branches to pre-rebase state
gh stack rebase --abort
| Flag | Description |
|---|---|
--downstack | Only rebase branches from trunk to the current branch |
--upstack | Only rebase branches from the current branch to the top |
--no-trunk | Skip trunk — only rebase stack branches onto each other (no fetch, no trunk rebase) |
--continue | Continue after resolving conflicts |
--abort | Abort and restore all branches |
--remote <name> | Remote to fetch from (use if multiple remotes exist) |
| Argument | Description |
|---|---|
[branch] | Target branch (defaults to the current branch) |
Conflict handling: See Handle rebase conflicts in the Workflows section for the full resolution workflow.
Merged PR detection: If a branch's PR was merged on GitHub, the rebase automatically handles this using --onto mode and correctly replays commits on top of the merge target.
Rerere (conflict memory): git rerere is enabled by init so previously resolved conflicts are auto-resolved in future rebases.
No-trunk mode: Use --no-trunk to skip fetching from the remote and rebasing with the trunk branch. Only inter-branch rebases are performed (branch 2 onto branch 1, branch 3 onto branch 2, etc.). Useful when you only need to align stack branches with each other without pulling upstream changes.
gh stack viewDisplay the current stack's branches, PR status, and recent commits. Always pass --json — without it, this command launches an interactive TUI that agents cannot operate.
# Always use --json
gh stack view --json
| Flag | Description |
|---|---|
--json | Output stack data as JSON to stdout (required for non-interactive use) |
--json output format:
{
"trunk": "main",
"currentBranch": "api-routes",
"branches": [
{
"name": "auth",
"head": "abc1234...",
"base": "def5678...",
"isCurrent": false,
"isMerged": true,
"needsRebase": false,
"pr": {
"number": 42,
"url": "https://github.com/owner/repo/pull/42",
"state": "MERGED"
}
},
{
"name": "api-routes",
"head": "789abcd...",
"base": "abc1234...",
"isCurrent": true,
"isMerged": false,
"needsRebase": false,
"pr": {
"number": 43,
"url": "https://github.com/owner/repo/pull/43",
"state": "OPEN"
}
}
]
}
Fields per branch:
name — branch namehead — current HEAD SHAbase — parent branch's HEAD SHA at last syncisCurrent — whether this is the checked-out branchisMerged — whether the PR has been mergedneedsRebase — whether the base branch is not an ancestor (non-linear history)pr — PR metadata (omitted if no PR exists). state is "OPEN" or "MERGED".Move between branches without remembering branch names. These commands are fully non-interactive.
gh stack up # Move up one branch (further from trunk)
gh stack up 3 # Move up three branches
gh stack down # Move down one branch (closer to trunk)
gh stack down 2 # Move down two branches
gh stack top # Jump to the top of the stack (furthest from trunk)
gh stack bottom # Jump to the bottom (first non-merged branch above trunk)
Navigation clamps to stack bounds. Merged branches are skipped when navigating from active branches.
gh stack checkoutCheck out a stack from a pull request number or branch name. Always provide an argument — running gh stack checkout without arguments triggers an interactive selection menu.
gh stack checkout <pr-number | branch>
# By PR number (pulls from GitHub)
gh stack checkout 42
# By branch name (local only)
gh stack checkout feature-auth
When a PR number is provided (e.g. 123), the command fetches the stack on GitHub, pulls the branches, and sets up the stack locally. If the stack already exists locally and matches, it switches to the branch.
⚠️ Agent warning: If the local and remote stacks have different branch compositions, this command triggers an interactive conflict-resolution prompt that cannot be bypassed with a flag. To avoid this: run
gh stack unstackfirst to remove the conflicting local stack, then retrygh stack checkout <pr-number>.
When a branch name is provided, the command resolves it against locally tracked stacks only. This is always safe for non-interactive use.
gh stack unstackTear down a stack so you can restructure it — remove a branch, reorder branches, rename branches, or make other large changes. After unstacking, use gh stack init to re-create the stack with the desired structure.
With no argument, the command targets the active stack — the one containing the currently checked out branch — unstacking it on GitHub and removing local tracking.
Provide a stack number to unstack a specific stack on GitHub. This works from anywhere in the repository, whether or not the stack is checked out locally — the number is unstacked directly through the GitHub API (like gh stack link, no local tracking required). If the stack is also tracked locally, its local tracking is removed as well.
gh stack unstack [<stack-number>] [flags]
# Tear down the current stack (locally and on GitHub), then rebuild
gh stack unstack
gh stack init --base main branch-2 branch-1 branch-3 # reordered
# Unstack a specific stack by its number, from anywhere in the repo
gh stack unstack 7
# Only remove local tracking (keep the stack on GitHub)
gh stack unstack --local
| Flag | Description |
|---|---|
--local | Only remove the stack locally (keep it on GitHub); never contacts GitHub |
Note for agents:
gh stack unstack <number>is a remote-first API wrapper — it unstacks on GitHub by number from anywhere in the repo, tracked locally or not, and is safe for non-interactive use.--localnever contacts GitHub; combining--localwith a number that isn't tracked locally is an error. An unknown stack number returns a "not found on GitHub" error (exit code 2).
✓ (success), ✗ (error), ⚠ (warning), ℹ (info).view --json) goes to stdout.2>/dev/null to suppress status messages if only data output is needed.| Code | Meaning | Agent action |
|---|---|---|
| 0 | Success | Proceed normally |
| 1 | Generic error | Read stderr for details; may indicate commit/push failure |
| 2 | Not in a stack | Run gh stack init to create a stack first |
| 3 | Rebase conflict | Parse stderr for conflicted file paths, resolve conflicts, run gh stack rebase --continue |
| 4 | GitHub API failure | Check gh auth status, retry the command |
| 5 | Invalid arguments | Fix the command invocation (check flags and arguments) |
| 6 | Disambiguation required | A branch belongs to multiple stacks. Run gh stack checkout <specific-branch> to switch to a non-shared branch first |
| 7 | Rebase already in progress | Run gh stack rebase --continue (after resolving conflicts) or gh stack rebase --abort to start over |
| 8 | Stack is locked | Another gh stack process is writing the stack file. Wait and retry — the lock times out after 5 seconds |
| 9 | Stacked PRs unavailable | The repository does not have stacked PRs enabled. submit will offer to create regular (unstacked) PRs in interactive mode |
--remote or config. If more than one remote is configured, pass --remote <name> or set remote.pushDefault in git config before running push, sync, or rebase.checkout with a branch name only works with locally tracked stacks. Use a PR number (e.g. gh stack checkout 123) to pull stacks from GitHub.submit. The title and body are generated from commit messages plus a footer. Use gh pr edit to modify PR title and body after creation.