* Make the TUIs adapt to light and dark terminal backgrounds
The submit, view, and modify TUIs were tuned for dark terminals. On light
or solarized-light backgrounds the result was hard to read and inverted:
primary text used ANSI white (invisible on white), dim chrome used light
grays (too faint), and accents used bright cyan (low contrast) — so
"active" things looked lighter than "disabled" ones.
Introduce a centralized, background-aware color palette and migrate all
three TUIs to it:
- internal/tui/shared/theme.go: a semantic palette of lipgloss.AdaptiveColor
values (primary/muted/faint text, chrome/border, accent, PR-state colors,
badge backgrounds, row shade, button, switch). lipgloss resolves the
light/dark variant per render from the terminal background, which Bubble
Tea detects at startup; terminals that don't report it fall back to dark,
preserving the original look.
- Replace every hardcoded ANSI color in shared/, submitview/, and
modifyview/ with palette roles. The four pre-rendered status icons now
render at use-time so their adaptive colors resolve correctly. The submit
markdown preview picks glamour's light or dark style from the detected
background.
- GH_STACK_THEME=auto|light|dark forces the palette for terminals that
mis-detect (some SSH/tmux setups); wired via the root command's
PersistentPreRun before any render. Documented in the README and CLI docs.
Neutral text/chrome use truecolor hex (GitHub Primer-inspired) for
predictability across themes, including solarized which repurposes ANSI
8-15; lipgloss downsamples on terminals without truecolor.
Tests verify the palette resolves differently for light vs dark and that
GH_STACK_THEME is honored.
* Apply background-aware colors to all command output
Background detection and the GH_STACK_THEME override (added for the TUIs)
only affected the interactive screens. Plain command output -- status
messages and interactive prompts -- went through the mgutz/ansi library
with fixed ANSI palette names (green/red/yellow/cyan/...), so it never
adapted to the terminal background and could read poorly on light or
solarized themes.
Unify everything on the same adaptive palette so all colors react to the
detected background and to GH_STACK_THEME.
- Extract internal/theme, a foundational package with no internal
dependencies, that owns:
- the background-aware lipgloss.AdaptiveColor palette (moved out of
internal/tui/shared),
- ApplyOverride(), the GH_STACK_THEME=auto|light|dark logic, and
- non-TUI colorizers (Success/Error/Warning/Blue/Magenta/Cyan/Gray/
Bold) plus FgSeqs(), which returns the raw start/reset escapes used
to color the user's echoed prompt input.
- internal/tui/shared/theme.go now re-exports the palette, so the TUI code
keeps referring to shared.ColorX unchanged.
- internal/config/config.go wires the Config.Color* funcs to the theme
colorizers and drops mgutz/ansi (now an indirect dependency only).
- cmd/utils.go colors the prompt icon and echoed input via theme.
- cmd/root.go calls theme.ApplyOverride() in PersistentPreRun.
Detection adds no cost: because the command package imports Bubble Tea,
its init() already triggers (and caches) the terminal background query for
every command, so the non-TUI colorizers just read the cached value.
Terminals that don't answer the query fall back to the dark palette;
GH_STACK_THEME=light|dark forces it. Colors are truecolor on capable
terminals and downsample to the nearest ANSI color elsewhere.
Tests: internal/theme covers palette adaptiveness, ApplyOverride, the
colorizers, and FgSeqs; a new internal/config test verifies the wired-up
Config.Color* funcs adapt to the background when color is enabled.
Docs: README and the CLI reference note that GH_STACK_THEME now controls
all colored output, not just the interactive screens.
No behavior change beyond colors.
20 KiB
title, description
| title | description |
|---|---|
| CLI Commands | Complete reference for all gh stack commands. |
Installation
gh extension install github/gh-stack
Requires the GitHub CLI (gh) v2.0+.
:::note[Authentication]
The gh stack CLI requires OAuth authentication via gh auth login. Personal access tokens (PATs) are not supported.
:::
Stack Management
gh stack init
Initialize a new stack in the current repository.
gh stack init [flags] [branches...]
Initializes a new stack locally. In interactive mode (no arguments), prompts for a branch name and offers to use the current branch as the first layer. If a branch name contains slashes (e.g., feat/api), prompts if you would like to use a prefix (e.g., feat/) for all branches in the stack.
When explicit branch names are given, existing branches are adopted automatically and any missing branches are created. The trunk defaults to the repository's default branch unless overridden with --base.
Use --numbered with --prefix to enable auto-incrementing branch names (prefix/01, prefix/02, …).
Enables git rerere automatically so that conflict resolutions are remembered across rebases.
| Flag | Description |
|---|---|
-b, --base <branch> |
Trunk branch for the stack (defaults to the repository's default branch) |
-p, --prefix <string> |
Set a branch name prefix for the stack |
-n, --numbered |
Use auto-incrementing numbered branch names (requires --prefix) |
Examples:
# Interactive — prompts for branch names
gh stack init
# Non-interactive — specify first branch upfront
gh stack init feature-auth
# Use a different trunk branch
gh stack init --base develop feature-auth
# Adopt or create multiple branches at once
gh stack init feature-auth feature-api feature-ui
# Set a prefix — prompts for a branch name suffix
gh stack init -p feat
# → type "auth" → creates feat/auth
# Use numbered auto-incrementing branch names
gh stack init -p feat --numbered
# → creates feat/01 automatically
gh stack add
Add a new branch on top of the current stack.
gh stack add [flags] [branch]
Creates a new branch at the current HEAD, adds it to the top of the stack, and checks it out. Must be run while on the topmost branch of a stack. If no branch name is given, prompts for one.
You can optionally stage changes and create a commit as part of the add flow. When -m is provided without an explicit branch name, the branch name is auto-generated. If the stack was created with --numbered, auto-generated names use numbered format (prefix/01, prefix/02); otherwise, date+slug format is used.
| Flag | Description |
|---|---|
-A, --all |
Stage all changes (including untracked files); requires -m |
-u, --update |
Stage changes to tracked files only; requires -m |
-m, --message <string> |
Create a commit with this message before creating the branch |
Note:
-Aand-uare mutually exclusive.
Examples:
# Create a branch by name
gh stack add api-routes
# Prompt for a branch name interactively
gh stack add
# Stage all changes, commit, and auto-generate the branch name
gh stack add -Am "Add login endpoint"
# Stage only tracked files, commit, and auto-generate the branch name
gh stack add -um "Fix auth bug"
# Commit already-staged changes and auto-generate the branch name
gh stack add -m "Add user model"
# Stage all changes, commit, and use an explicit branch name
gh stack add -Am "Add tests" test-layer
# Stage only tracked files, commit, and use an explicit branch name
gh stack add -um "Update docs" docs-layer
gh stack view
View the current stack.
gh stack view [flags]
Shows all branches in the stack, their ordering, PR links, and the most recent commit with a relative timestamp. Output is piped through a pager (respects GIT_PAGER, PAGER, or defaults to less -R).
| Flag | Description |
|---|---|
-s, --short |
Compact output (branch names only) |
--json |
Output stack data as JSON |
Examples:
gh stack view
gh stack view --short
gh stack view --json
gh stack checkout
Check out a stack from a pull request number, URL, or branch name.
gh stack checkout [<pr-number> | <pr-url> | <branch>]
When a PR number or URL is provided (e.g., 123 or https://github.com/owner/repo/pull/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. If the local and remote stacks have different compositions, you'll be prompted to resolve the conflict.
When a branch name is provided, the command resolves it against locally tracked stacks only.
When run without arguments in an interactive terminal, shows a menu of all locally available stacks to choose from.
Examples:
# Check out a stack by PR number
gh stack checkout 42
# Check out a stack by PR URL
gh stack checkout https://github.com/owner/repo/pull/42
# Check out a stack by branch name (local only)
gh stack checkout feature-auth
# Interactive — select from locally tracked stacks
gh stack checkout
gh stack modify
Interactively restructure the current stack.
gh stack modify [flags]
Opens an interactive terminal UI for restructuring a stack. All changes are staged in the TUI and applied together when you press Ctrl+S. Branches from merged PRs cannot be modified.
| Flag | Description |
|---|---|
--continue |
Continue after resolving conflicts |
--abort |
Abort the modify session and restore the stack to its pre-modify state |
Preconditions:
The command checks these conditions before opening the TUI:
- Must have an active stack checked out locally
- Working tree must be clean (no uncommitted changes)
- No rebase in progress
- No PR in the stack is queued for merge
- Commit history must be linear (no merge commits, no diverged branches)
Operations:
| Operation | Key | Effect |
|---|---|---|
| Drop | x |
Remove branch and its commits from stack. Local branch and associated PR are preserved. |
| Fold down | d |
Absorb commits into branch below (toward trunk). Folded branch removed from stack. |
| Fold up | u |
Absorb commits into branch above (away from trunk). Folded branch removed from stack. |
| Insert below | i |
Insert a new empty branch below the cursor (toward trunk). |
| Insert above | I |
Insert a new empty branch above the cursor (away from trunk). |
| Move down | Shift+↓ |
Reorder branch down (toward trunk) in the stack |
| Move up | Shift+↑ |
Reorder branch up (away from trunk) in the stack |
| Rename | r |
Rename the branch (opens inline prompt) |
| Undo | z |
Undo the last staged action |
Apply phase:
When you press Ctrl+S, the staged changes are applied by renaming branches, inserting new branches, folding/dropping branches, and running a cascading rebase to create a linear commit history with the desired stack state.
If a rebase conflict occurs, you can:
- Resolve conflicts, stage files, and run
gh stack modify --continue - Or run
gh stack modify --abortto abort the operation and restore the stack to the pre-modify state
After modifying:
If a stack of PRs has been created on GitHub, run gh stack submit to push the updated branches and recreate the stack. The old stack is automatically replaced.
Examples:
# Open the interactive modify TUI
gh stack modify
# Continue after resolving a conflict
gh stack modify --continue
# Abort and restore to the previous state
gh stack modify --abort
gh stack unstack
Remove a stack from local tracking and delete it on GitHub. Also available as gh stack delete.
gh stack unstack [flags]
You must have a branch from the stack checked out locally. The command targets the active stack — the one that contains the currently checked out branch.
Deletes the stack on GitHub first, if it exists, then removes it from local tracking. If the remote deletion fails, the local state is left untouched so you can retry. Use --local to skip the remote deletion and only remove local tracking.
This is useful when you need to restructure a stack — remove a branch, insert 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 — existing branches are adopted automatically.
| Flag | Description |
|---|---|
--local |
Only delete the stack locally (keep it on GitHub) |
Examples:
# Delete the stack on GitHub and remove local tracking
gh stack unstack
# Only remove local tracking
gh stack unstack --local
Remote Operations
gh stack submit
Push all branches and create/update PRs and the stack on GitHub.
gh stack submit [flags]
Creates a Stacked PR for every branch in the stack, pushing branches to the remote. After creating PRs, submit automatically creates a Stack on GitHub to link the PRs together. If the stack already exists on GitHub (e.g., from a previous submit), new PRs are added to the existing stack.
In an interactive terminal, submit opens a full-screen editor on a single screen:
- Left panel — every branch without a PR is included by default; deselect any you don't want to submit with Ctrl+X. Because each PR builds on the branch below it, deselecting a branch also deselects the ones stacked above it, and re-including a branch re-includes the ones below it that it depends on. Branches that already have a PR (open, draft, queued, or merged) are shown for context but are locked; edit those on the web.
- Right panel — for the focused branch, draft the title, description (pre-filled from your repo's PR template or commits, with a Glamour markdown preview and
$EDITORescape), and whether it opens ready for review or as a draft (a ready ↔ draft toggle). Focusing a locked branch shows a read-only card with a link to its PR (o to open in the browser).
Press Ctrl+S to submit all included PRs at once. The editor supports both keyboard and mouse input. Pass --auto (or run in a non-interactive terminal, such as CI) to skip the editor and use auto-generated titles.
In the editor, new PRs default to ready for review; flip any PR to draft with the ready ↔ draft toggle. With --auto, new PRs are created as drafts unless you pass --open.
| Flag | Description |
|---|---|
--auto |
Skip the editor and use auto-generated PR titles |
--open |
Create new PRs as ready for review instead of drafts, and mark existing PRs as ready for review |
--remote <name> |
Remote to push to (defaults to auto-detected remote) |
Examples:
gh stack submit
gh stack submit --auto
gh stack submit --open
gh stack sync
Fetch, rebase, push, and sync PR state in a single command.
gh stack sync [flags]
Performs a safe, non-interactive synchronization of the entire stack:
- Fetch — fetches the latest changes from
origin. - Fast-forward trunk — fast-forwards the trunk branch to match the remote (skips if diverged).
- Cascade rebase — rebases all stack branches onto their updated parents (only if trunk moved). If a conflict is detected, all branches are restored to their original state, and you are advised to run
gh stack rebaseto resolve conflicts interactively. - Push — pushes all branches (uses
--force-with-leaseif a rebase occurred). - Sync PRs — syncs PR state from GitHub and reports the status of each PR.
- Prune — in interactive terminals, prompts to delete local branches for merged PRs. Use
--pruneto prune automatically.
| Flag | Description |
|---|---|
--remote <name> |
Remote to fetch from and push to (defaults to auto-detected remote) |
--prune |
Delete local branches for merged PRs |
Examples:
gh stack sync
# Sync and automatically prune merged branches
gh stack sync --prune
gh stack rebase
Pull from remote and do a cascading rebase across the stack.
gh stack rebase [flags] [branch]
Fetches the latest changes from origin, then ensures each branch in the stack has the tip of the previous layer in its commit history. Rebases branches in order from trunk upward.
If a branch's PR has been merged, the rebase automatically switches to --onto mode to correctly replay commits on top of the merge target.
If a rebase conflict occurs, the operation pauses and prints the conflicted files with line numbers. Resolve the conflicts, stage with git add, and continue with --continue. To undo the entire rebase, use --abort to restore all branches to their pre-rebase state.
| 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 the rebase after resolving conflicts |
--abort |
Abort the rebase and restore all branches to their pre-rebase state |
--remote <name> |
Remote to fetch from (defaults to auto-detected remote) |
--committer-date-is-author-date |
Set the committer date to the author date during rebase. Alias: --preserve-dates |
| Argument | Description |
|---|---|
[branch] |
Target branch (defaults to the current branch) |
Examples:
# Rebase the entire stack
gh stack rebase
# Only rebase branches below the current one
gh stack rebase --downstack
# Only rebase branches above the current one
gh stack rebase --upstack
# Rebase stack branches without pulling from or rebasing with trunk
gh stack rebase --no-trunk
# After resolving a conflict
gh stack rebase --continue
# Abort rebase and restore everything
gh stack rebase --abort
# Rebase and preserve committer date as author date
gh stack rebase --committer-date-is-author-date
gh stack push
Push all branches in the current stack to the remote.
gh stack push [flags]
Pushes every branch to the remote using --force-with-lease --atomic. This is a lightweight wrapper around git push that knows about all branches in the stack. It does not create or update pull requests — use gh stack submit for that.
| Flag | Description |
|---|---|
--remote <name> |
Remote to push to (defaults to auto-detected remote) |
Examples:
gh stack push
gh stack push --remote upstream
gh stack link
Link PRs into a stack on GitHub without local tracking.
gh stack link [flags] <branch-or-pr> <branch-or-pr> [...]
Creates or updates a stack on GitHub from branch names or PR numbers/URLs. This command does not create or modify any gh-stack local tracking state. It is designed for users who manage branches with other tools locally (e.g., jj, Sapling, git-town) and want to simply open a stack of PRs.
Arguments are provided in stack order (bottom to top). Branch arguments are automatically pushed to the remote before creating or looking up PRs. For branches that already have open PRs, those PRs are used. For branches without PRs, new PRs are created automatically with the correct base branch chaining. Existing PRs whose base branch doesn't match the expected chain are corrected automatically.
If the PRs are not yet in a stack, a new stack is created. If some of the PRs are already in a stack, the existing stack is updated to include the new PRs. Existing PRs are never removed from a stack — the update is additive only.
| 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 (defaults to auto-detected remote) |
Examples:
# Link branches into a stack (pushes, creates PRs, creates stack)
gh stack link feature-auth feature-api feature-ui
# Link existing PRs by number
gh stack link 10 20 30
# Link existing PRs by URL
gh stack link https://github.com/owner/repo/pull/10 https://github.com/owner/repo/pull/20
# Add branches to an existing stack of PRs
gh stack link 42 43 feature-auth feature-ui
# Use a different base branch and mark PRs as ready for review
gh stack link --base develop --open feat-a feat-b feat-c
Navigation
Move between branches in the current stack without having to remember branch names. The bottom of the stack is the branch closest to the trunk, and the top is furthest from it. up moves away from trunk; down moves toward it.
All navigation commands clamp to the bounds of the stack — moving up from the top or down from the bottom is a no-op with a message.
gh stack switch
Interactively switch to another branch in the stack.
gh stack switch
Shows an interactive picker listing all branches in the current stack, ordered from top (furthest from trunk) to bottom (closest to trunk) with their position number. Select a branch to check it out.
Requires an interactive terminal.
Examples:
gh stack switch
# → Select a branch in the stack to switch to
# 5. frontend
# 4. api-endpoints
# 3. auth-layer
# 2. db-schema
# 1. config-setup
gh stack up
Move up toward the top of the stack (away from trunk).
gh stack up [n]
Moves up n branches (default 1). If you're on the trunk branch, up moves to the first stack branch.
Examples:
gh stack up # move up one layer
gh stack up 3 # move up three layers
gh stack down
Move down toward the bottom of the stack (toward trunk).
gh stack down [n]
Moves down n branches (default 1).
Examples:
gh stack down # move down one layer
gh stack down 2 # move down two layers
gh stack top
Jump to the top of the stack.
gh stack top
Checks out the branch furthest from the trunk.
gh stack bottom
Jump to the bottom of the stack.
gh stack bottom
Checks out the branch closest to the trunk.
gh stack trunk
Jump to the trunk branch.
gh stack trunk
Checks out the trunk branch of the current stack (e.g., main). You must be on a branch that is part of a stack.
Utilities
gh stack alias
Create a short command alias so you can type less.
gh stack alias [flags] [name]
Installs a small wrapper script into ~/.local/bin/ that forwards all arguments to gh stack. The default alias name is gs, but you can choose any name by passing it as an argument. After setup, you can run gs push instead of gh stack push.
On Windows, automatic alias creation is not supported — the command prints manual instructions for creating a batch file or PowerShell function.
| Flag | Description |
|---|---|
--remove |
Remove a previously created alias |
Examples:
# Create the default alias (gs)
gh stack alias
# → now "gs push", "gs view", etc. all work
# Create a custom alias
gh stack alias gst
# Remove an alias
gh stack alias --remove
gh stack alias --remove gst
gh stack feedback
Share feedback about gh-stack.
gh stack feedback [title]
Opens a GitHub Discussion in the gh-stack repository to submit feedback. Optionally provide a title for the discussion post.
Examples:
gh stack feedback
gh stack feedback "Support for reordering branches"
Environment Variables
| Variable | Values | Description |
|---|---|---|
GH_STACK_THEME |
auto (default), light, dark |
Controls the color palette of the interactive screens (submit, modify, view) and all colored command output. Colors adapt to your terminal background automatically; set this to force the light or dark palette when a terminal doesn't report its background (some SSH or tmux setups). |
# Force the light palette for one command
GH_STACK_THEME=light gh stack view
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Generic error |
| 2 | Not in a stack / stack not found |
| 3 | Rebase conflict |
| 4 | GitHub API failure |
| 5 | Invalid arguments or flags |
| 6 | Disambiguation required (branch belongs to multiple stacks) |
| 7 | Rebase already in progress |
| 8 | Stack is locked by another process |
| 9 | Stacked PRs not enabled for this repository |