Skip to main content

pr-bot

PR lifecycle helpers for a main + release/X.Y + {feat,fix,hotfix}/<slug> branching model. Linear history, squash merges to main, fast-forward-only main, semver (X.Y.Z) tags on release branches.

Ships as:

  • a single Python CLI (pr-bot) on PyPI — one install, works in every repo
  • a sibling CLI (toolbar) for repo bootstrap + workspace ops
  • a Claude Code plugin shipping ~28 slash commands (/pr-*, /release-*, /hotfix-*, /stack-*, /policy-*, /toolbar-*) that thinly wrap the CLI
  • a Textual terminal dashboard (pr-bot ui)

The model

main ─────●──●──●──●──●──────●──●──●──────────●────────▶  (FF-only, linear)
            \             \                    \
             feat/login    fix/off-by-one       release/1.4 ──●──●──▶
                                                               │  │
                                                               │  └── tag 1.4.1
                                                               │
                                                               hotfix/npe-on-logout
  • Branches off main: feat/<slug>, fix/<slug>. Squash-merged back into main; branch is deleted on merge.
  • Release branch: release/X.Y is cut from a commit on main. Long-lived. Receives only hotfixes.
  • Tags: X.Y.Z (annotated) placed on a commit on the matching release/X.Y branch.
  • Hotfixes: hotfix/<slug> off release/X.Y, PR'd back to release/X.Y, then forward-ported to main as a fix/<slug> PR.

Prerequisites

Tool Minimum Why
uv 0.4+ Runs the CLI hermetically via uvx; bootstraps Python 3.14
git 2.30+ --force-with-lease, symbolic-ref --short
gh 2.40+ pr create/merge, repo create, release create

gh must be authenticated (gh auth status). For git pushes, run gh auth setup-git once — pr-bot relies on gh's credential helper.

Install

Recommended: PyPI via uvx

Every slash command invokes the CLI through uvx, which fetches pr-bot from PyPI into a cached isolated env per invocation. No shared venv to manage; upgrades are atomic (bump the version, invalidate the cache).

# verify
uvx pr-bot --version

Pin to a specific release for reproducibility:

export PR_BOT_SOURCE='pr-bot==0.2.0'              # exact version
export PR_BOT_SOURCE='git+https://github.com/andrewrothstein/pr-bot@0.2.0'
export PR_BOT_SOURCE=/path/to/clone                # dev loop

The slash commands default to PR_BOT_SOURCE=pr-bot (= latest on PyPI).

Convenience: plain CLI on PATH

If you want pr-bot on your PATH instead of going through uvx each time:

uv tool install pr-bot

This creates a shared user venv — fine for a workstation, but it's one global install, not per-invocation isolation.

Slash commands

Recommended: install as a Claude Code plugin

# inside Claude Code:
/plugin marketplace add andrewrothstein/pr-bot
/plugin install pr-bot@pr-bot

That's it. All /pr-*, /release-*, /hotfix-*, /stack-*, /policy-*, /toolbar-* slash commands are available in any project once the plugin is installed.

Alternative: symlink locally

If you'd rather use a clone of this repo directly:

ln -s /path/to/pr-bot/commands ~/.claude/commands/pr-bot
# or, for a project-local install:
ln -s /path/to/pr-bot/commands .claude/commands/pr-bot

(Plain cp also works if you prefer a detached copy.)

Commands

Everything is pr-bot <group> <subcommand>. Each has a slash-command twin.

Starting and landing a PR

Command Slash What it does
pr-bot pr start feat <slug> /pr-start Fetch + FF main, create feat/<slug> off it.
pr-bot pr start fix <slug> /pr-start Same, but fix/<slug>.
pr-bot pr open [--draft] /pr-open Push current branch, gh pr create against main.
pr-bot pr sync /pr-sync Rebase onto origin/main, force-push with lease.
pr-bot pr ready /pr-ready Mark draft PR ready for review.
pr-bot pr merge [--auto] /pr-merge Squash-merge, delete remote branch, fetch + FF main, prune local.
pr-bot pr cleanup /pr-cleanup Finish the merge dance after a GitHub-UI merge (FF main + delete local branch).
pr-bot pr status /pr-status Ahead/behind main + open PR state.

Releases

Command Slash What it does
pr-bot release cut X.Y /release-cut Create release/X.Y off main (or --from <ref>), push.
pr-bot release prepare X.Y.Z /release-prepare Bump pyproject.toml, regenerate CHANGELOG.md, draft missing notes, commit, push.
pr-bot release tag X.Y.Z /release-tag Tag X.Y.Z on release/X.Y, push, create GitHub release. Accepts --notes-file.
pr-bot release notes-draft X.Y.Z /release-notes-draft Auto-draft docs/releases/X.Y.Z.md from PRs merged since the last tag, grouped by conventional-commit prefix.
pr-bot release changelog /release-changelog Regenerate CHANGELOG.md from docs/releases/*.md (sorted desc).
pr-bot release list /release-list List release branches + their highest tag.
pr-bot release next-patch — Print the next unused patch version for the current series.

Hotfixes

Command Slash What it does
pr-bot hotfix start <slug> --on X.Y /hotfix-start hotfix/<slug> off release/X.Y.
pr-bot hotfix open --on X.Y /hotfix-open Push + gh pr create against release/X.Y.
pr-bot hotfix forward-port <sha> /hotfix-forward New fix/<slug> off main, cherry-picks the hotfix commit.

Sanity

Command Slash What it does
pr-bot doctor /pr-doctor Check main is linear, branches/tags match the model, remote wired.
pr-bot policy apply /policy-apply Push .pr-bot.toml policy to GitHub branch protection. See docs/policy.md.
pr-bot policy check /policy-check Diff GitHub's actual branch protection vs. declared policy.
pr-bot ui — Launch the Textual terminal dashboard. See docs/ui.md.

Typical workflows

Feature PR

pr-bot pr start feat add-login-button
# …hack hack hack; commit as usual…
pr-bot pr open --draft
# …more commits…
pr-bot pr sync          # keep up with main
pr-bot pr ready
pr-bot pr merge         # squashes, deletes branch, fast-forwards main

Cut and ship 1.4.0

pr-bot release cut 1.4                                  # creates release/1.4
pr-bot release prepare 1.4.0                            # bumps pyproject + auto-drafts notes + commits + pushes
pr-bot release tag 1.4.0 --notes-file docs/releases/1.4.0.md
pr-bot release list                                     # confirm

Hotfix for a released version

pr-bot hotfix start fix-npe-on-logout --on 1.4
# …fix, commit…
pr-bot hotfix open --on 1.4       # PR into release/1.4
# …merge via GitHub UI or gh pr merge, whichever your repo uses…
pr-bot release tag 1.4.1          # ship it
pr-bot hotfix forward-port <sha>  # carry the fix to main as fix/<slug>
pr-bot pr open
pr-bot pr merge

Why these rules

  • Linear main makes git bisect, git log, and blame actually useful.
  • Squash merges give one commit per PR, keeping PR == unit of change.
  • FF-only main means main never absorbs stale state; rebasing is the only way to integrate.
  • release/X.Y + X.Y.Z tags cleanly separate "what's next" from "what shipped", and let hotfixes target exactly one released line.

Stacked PRs

For changes too large for one PR but logically a unit, build a chain of dependent PRs and land them in order:

Command Slash What it does
pr-bot stack new <slug-1> <slug-2> [...] /stack-new Create a chain of PR branches (each branched off the previous).
pr-bot stack push /stack-push Push every branch + open/update PRs with cross-link <!-- pr-bot:stack --> block.
pr-bot stack sync /stack-sync Rebase the chain onto origin/main; force-push each branch.
pr-bot stack land [--all] /stack-land Squash-merge the bottom branch + pop from state. --all lands the whole stack.
pr-bot stack status /stack-status Show branches, PR numbers, draft state.
pr-bot stack abandon /stack-abandon Delete the stack state file (branches/PRs kept).

State lives in .git/pr-bot/stack.json (per-clone, never committed). See docs/design-pr-stack.md for the design.

toolbar — workspace-admin helpers

Ships alongside pr-bot from this same repo. Scope: things you do around a repo (create it, wire up a remote), as opposed to what pr-bot does inside one once it exists.

Command Slash What it does
toolbar repo info /toolbar-repo-info Dry-run: show the resolved target (org/name, URL, host).
toolbar repo create [--public] [--push] /toolbar-repo-create gh repo create (private by default) + set origin; --push optional.
toolbar repo template <name> /toolbar-repo-template Bootstrap a new project: render skeleton + git init + cut release/0.1 + push to GitHub.
toolbar repo push-main — Push local main/master after a clean-tree sanity check.

Honors two env vars for defaults (flags override):

GH_MIRROR   (default: https://github.com)
GH_ORG      (default: andrewrothstein)

GH_HOST (from the gh CLI) takes precedence over the host parsed from GH_MIRROR — useful when the browser URL differs from the API host.

Bootstrap a brand-new repo

If you want the full pr-bot model wired up from scratch (pyproject + ruff

  • ratchet-pinned CI + CLAUDE.md + AGENTS.md + LICENSE + a cut release/0.1):
toolbar repo template my-thing --description "What it does"
# → creates ./my-thing/, git init, scaffolds, gh repo create --private --push

Or, if you already have a repo and just need it pushed up:

cd /path/to/newproj
git init -b main && git commit --allow-empty -m "initial"

toolbar repo info                 # confirm target
toolbar repo create --push        # private repo + push main

Troubleshooting

git push fails with "Repository not found" right after toolbar repo create. Run gh auth setup-git once. gh wires itself in as a git credential helper for github.com (and any GH_HOST you've set). Without it, git push has no token and gets a 401, which GitHub surfaces as "not found".

pr-bot pr sync stops on a rebase conflict. That's the expected behavior — pr-bot does not auto-resolve conflicts. Resolve with the usual git add / git rebase --continue flow, then git push --force-with-lease origin <branch>.

Want a pinned version of pr-bot instead of always-latest? Override PR_BOT_SOURCE:

export PR_BOT_SOURCE='pr-bot==0.2.0'                            # PyPI
export PR_BOT_SOURCE='git+https://github.com/andrewrothstein/pr-bot@0.2.0'
export PR_BOT_SOURCE=/path/to/your/clone                        # dev loop

Contributing

This repo dogfoods its own model. For every change:

pr-bot pr start feat <slug>
# …edit, commit…
uv run pytest -q && uv run ruff check src tests
pr-bot pr open --draft
pr-bot pr ready
pr-bot pr merge

Pure logic (src/pr_bot/names.py, pr_bot/policy.py, pr_bot/stack.py, pr_bot/changelog.py, pr_bot/release_notes.py, pr_bot/pyproject.py, toolbar/env.py, toolbar/template.py) is unit-tested in tests/. Subprocess wrappers (pr_bot.git, pr_bot.gh) and the higher-level command modules (pr_bot.commands.{pr,release,hotfix,stack}) are integration-tested against a real git repo in tmp_path plus a gh shim on PATH. Total: 148 tests.

Error messages should start with pr-bot: or toolbar: so callers (and agents) can grep them.

Versioning

SemVer on the matching release line.

  • 0.x.y — preview. CLI surface, file formats, and the importable Python API may change between minor releases.
  • 1.0+ — stable surfaces locked. See docs/stability.md for the full list of what's stable, what's experimental, and the deprecation policy.

Publishing

Releases to PyPI happen automatically on tag push (X.Y.Z) via a Trusted Publisher — no API tokens in repo secrets. See docs/publishing.md for the one-time PyPI setup and the per-release flow.

See also

Metadata

Release files for pr-bot 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pr-bot 0.3.0
File Size Uploaded
pr_bot-0.3.0.tar.gz 93.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pr-bot 0.3.0
File Interpreter ABI Platform
pr_bot-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 144.0 kB

Release files / pr_bot-0.3.0.tar.gz

Download URL pr_bot-0.3.0.tar.gz
Size 93.1 kB
Tags Source
SHA-256 checksum
How to use checksums
5268a0616397805028f5017221c409072f168e467719f12d038882b1911dd48e
BLAKE2b-256 checksum
How to use checksums
ab37c73d50c8fbc63a556fe4a2da23f6374ff88e441063ee42b875ddd72ab755
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 25, 2026.

Transparency log

Release files / pr_bot-0.3.0-py3-none-any.whl

Download URL pr_bot-0.3.0-py3-none-any.whl
Size 51.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6d940610f0c9b5ff39647f2a3e871b55ef085c3d19fc39dbdbad0506c5d04a16
BLAKE2b-256 checksum
How to use checksums
cc29cb8e0c2e3e9e1d4719916d1b234efaf0e11aa826b5f53e25185108603890
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page