# spr: Stacked Pull Requests From One Branch

> spr (ejoffe/spr) turns each commit on a branch into a separate GitHub PR. Reference for spr update, spr status, --all, and the GitHub token errors in CI.

Source: https://latchkey.dev/learn/command-reference/spr-stacked-prs  
Updated: 2026-06-30

spr update pushes each commit on your branch as its own stacked GitHub PR and keeps them in sync.

spr implements a Phabricator-style workflow on GitHub: one commit equals one PR. spr update is the workhorse that creates and refreshes the whole stack.

## What it does

spr reads the commits on your local branch, assigns each a stable commit-id (via a git commit-msg hook), pushes one remote branch per commit, and opens or updates a GitHub PR for each, chaining their bases. spr status lists the stack and spr merge merges the bottom PR.

## Common usage

```Terminal
# create/update the stack of PRs
spr update
# show the state of each PR in the stack
spr status
# merge the bottom-most PR once it is approved and green
spr merge
```

## Options

| Command / Flag | What it does |
| --- | --- |
| spr update | Push commits and create/update their PRs |
| spr status | Print the state of each PR in the stack |
| spr merge | Merge the bottom PR of the stack |
| --count <n> | Limit spr update to the top n commits |
| github.token (config) | GitHub token spr uses to talk to the API |

## In CI

spr reads its GitHub token from the repo config or a token file; export it (spr respects a configured GitHub token) rather than logging in interactively. It requires a linear commit history with commit-ids, so run it on a clean branch and let its commit-msg hook add the ids.

## Common errors in CI

"unable to find GitHub OAuth token" or "GitHub token is required" means no token is configured. "commit ... does not have a commit-id" means the commit-msg hook did not run; amend to add the id. "422 ... A pull request already exists" indicates a stale remote branch. "not a git repository" means it ran outside a checkout.

## Using this in CI

CI checkouts are shallow and detached by default, which changes the answer this command gives you. Commands that read history, branch names, or tags need the checkout configured for it.

```.github/workflows/ci.yml
- uses: actions/checkout@v4
  with:
    fetch-depth: 0   # history, tags, and git describe all need this

- run: |
    git rev-parse --is-shallow-repository   # expect false
    git rev-parse --abbrev-ref HEAD          # prints HEAD when detached
```

> `git rev-parse --abbrev-ref HEAD` returns the literal string `HEAD` on a detached checkout rather than a branch name. On GitHub Actions read `github.ref_name` instead; the git command cannot know what it was checked out for.

## FAQ

### spr: Stacked Pull Requests From One Branch?

spr implements a Phabricator-style workflow on GitHub: one commit equals one PR. spr update is the workhorse that creates and refreshes the whole stack.

### What it does?

spr reads the commits on your local branch, assigns each a stable commit-id (via a git commit-msg hook), pushes one remote branch per commit, and opens or updates a GitHub PR for each, chaining their bases. spr status lists the stack and spr merge merges the bottom PR.

### In CI?

spr reads its GitHub token from the repo config or a token file; export it (spr respects a configured GitHub token) rather than logging in interactively. It requires a linear commit history with commit-ids, so run it on a clean branch and let its commit-msg hook add the ids.

### Common errors in CI?

"unable to find GitHub OAuth token" or "GitHub token is required" means no token is configured. "commit ... does not have a commit-id" means the commit-msg hook did not run; amend to add the id. "422 ... A pull request already exists" indicates a stale remote branch. "not a git repository" means it ran outside a checkout.

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
