Stagecoach
Stagecoach writes your commit messages using the AI agent you already pay for.
No API key. No per-token billing. It shells out to Claude Code, Codex, pi, opencode, agy, or Cursor — whatever you already have installed — and spends your existing coding-plan quota instead. Stage while it thinks; it commits only what was staged when it started, atomically, and can never corrupt your repo. With a dirty working tree and nothing staged, it automatically decomposes your changes into a sequence of logically-coherent commits.
A snapshot-based AI commit message generator that uses YOUR local CLI agent. v2.1 adds payload exclusions, message shaping, git hook mode, editor/git integrations, --edit/--push, and model discovery; v3.0 adds stagecoach upgrade (delegate-first self-update) and expands distribution (Homebrew, Scoop, Chocolatey, PowerShell installer, npm, Nix, mise/asdf) — see Features below.

Lazygit Demo
https://github.com/user-attachments/assets/89dd318d-5f75-4d25-af7d-4623fe8cfe23
Why not opencommit/aicommits?
The incumbent tools — opencommit, aicommits — own the HTTP call to the model, so they can normalize providers, handle retries, and abstract auth. Once you own the HTTP call, you cannot use a coding-plan subscription, because that subscription is not reachable over the public API. Not every plan is locked down this way — a few are permissive (Opencode's, for one) — but the most popular ones like Anthropic, Antigravity and Cursor gate their quota to the official harness. The quota lives behind your agent's CLI either way, which is exactly why stagecoach shells out to that CLI instead of opening its own connection.
Stagecoach inverts the architecture: it shells out to your installed CLI agent, trading provider normalization for quota reuse — the agent brings its own auth and billing. That trade-off — give up control of the model call in exchange for access to the user's existing quota — is the entire product.
|
opencommit / aicommits |
Stagecoach |
| Auth |
API key required |
None — uses your agent's existing auth |
| Architecture |
Owns the HTTP call |
Shells out to your CLI agent |
| Billing |
Per-token |
Your existing coding-plan quota |
| Stage while generating |
No |
Yes (snapshot-based) |
| Multi-commit decomposition |
No |
Yes (auto-decompose dirty tree into N logical commits) |
| Per-role model routing |
No |
Yes (planner/stager/message/arbiter — right model for the right job) |
Which coding plans actually gate their quota?
How strictly a coding plan's quota is tied to its official harness varies by provider. A few are
permissive; the popular ones are not — and that distinction is the whole reason stagecoach shells
out to your agent rather than calling an API.
- Anthropic (Claude Code) — strict. Plan quota is gated to the Claude Code harness and isn't
reachable over the public API.
- Antigravity — strict (and newly arriving). Quota is reserved for the harness.
- Cursor — has explicit policies against use outside its own harness.
Net: Most providers cares about keeping you on their harness. Opencode and Pi are the outliers.
Features
Stagecoach does one thing — commit messages — and a few things around them.
| Capability |
Description |
| Multi-commit decomposition |
Auto-decompose a dirty, un-staged tree into N logical commits (planner → stager → message → arbiter). A start-of-run freeze means a concurrent edit during the run can never enter a commit — including across the leftover-reconciliation arbiter; the planner partitions per file and leans toward a soft count target (how it works · flags). |
| Payload exclusions |
.stagecoachignore / --exclude hide a file's diff from the model — never from the commit (docs). |
| Payload optimization |
The diff sent to your agent is trimmed and budgeted — rename-aware (-M), reduced-context (-U1), led by a compact file skeleton, and optionally capped to your model's context window via token_limit — a closed-loop guarantee that the assembled prompt never exceeds the limit (how it works · knobs). |
| Multi-turn fallback |
Lossless multi-turn fallback: when a one-shot generation of a large diff fails, stagecoach re-delivers the full diff across session turns so the message still lands — no truncation, no extra commits (how it works · knobs). |
| Work-description mode |
--work-description "..." leads the prompt with your description + the file skeleton, and the model reads staged file diffs on demand via READ <path> (no tool-calling needed; runs in bare mode on every provider) (how it works · flags). |
| Message shaping |
--format (auto, conventional, gitmoji, plain; append +body to force a subject+body), --locale, --context, --template (docs). |
| Git hook mode |
stagecoach hook install fills the message on git commit — pre-commit hooks honored, never blocks (docs). |
Commit hooks on every stagecoach commit |
As of v2.4, your repo's pre-commit → prepare-commit-msg → commit-msg → post-commit hooks run around every stagecoach commit, scoped to the frozen snapshot (atomic + stage-while-generating preserved); --no-verify mirrors git (how it works). |
| Tool integrations |
stagecoach integrate install git-alias lazygit wires git stagecoach and a lazygit keybind (docs). |
--edit / --push |
Review in $EDITOR before the atomic commit; push after a clean run (docs). |
| Discovery |
stagecoach models and config init --interactive for guided setup (docs). |
Installation
Prerequisite: a coding-agent CLI already installed and on $PATH (pi, Claude Code, opencode, Codex, Cursor, or agy).
[!NOTE]
Stagecoach ships via the package-managed channels below, and stagecoach upgrade keeps any of them current — see Updating below.
Package managers
Pick whichever channel matches your platform or workflow:
# Homebrew (macOS / Linuxbrew)
brew install dabstractor/stagecoach/stagecoach
# Windows (Scoop)
scoop bucket add stagecoach https://github.com/dabstractor/stagecoach-bucket
scoop install stagecoach/stagecoach
# Windows (Chocolatey)
choco install stagecoach
# Windows (PowerShell installer — no package manager needed)
irm https://github.com/dabstractor/stagecoach/raw/main/install.ps1 | iex
# npm (zero-install trial: npx stagecoach-ai; or global install)
npm install -g stagecoach-ai
# Nix (flake — no channel/registry needed)
nix profile install github:dabstractor/stagecoach
# mise / asdf (version-manager users)
mise use stagecoach@latest # or: asdf plugin add stagecoach && asdf install stagecoach latest
# Debian/Ubuntu (apt repo — updates flow through apt)
curl -fsSL https://github.com/dabstractor/stagecoach/raw/main/apt-archive-keyring.asc \
| sudo gpg --dearmor -o /etc/apt/keyrings/stagecoach.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/stagecoach.gpg] https://dabstractor.github.io/stagecoach/apt stable main" \
| sudo tee /etc/apt/sources.list.d/stagecoach.list
sudo apt update && sudo apt install stagecoach
# Fedora/RHEL (dnf repo — updates flow through dnf)
sudo curl -fsSL https://dabstractor.github.io/stagecoach/rpm/stagecoach.repo -o /etc/yum.repos.d/stagecoach.repo
sudo dnf install stagecoach
# No repo? Grab the raw .deb/.rpm from the latest GitHub Release and install directly:
# sudo apt install ./stagecoach_<version>_linux_amd64.deb
# sudo dnf install ./stagecoach_<version>_linux_amd64.rpm
# Go install (anywhere with Go)
go install github.com/dabstractor/stagecoach/cmd/stagecoach@latest
# Direct binary (curl|sh one-liner from GitHub Releases)
curl -fsSL https://github.com/dabstractor/stagecoach/raw/main/install.sh | bash
Verify:
stagecoach --version
Build from source
Requires Go 1.22+:
git clone https://github.com/dabstractor/stagecoach.git
cd stagecoach
make install # installs the binary to $GOPATH/bin
Ensure $GOPATH/bin (usually ~/go/bin) is on your $PATH, then verify:
stagecoach --version # stagecoach version dev
[!TIP]
If you keep your user binaries elsewhere (e.g. ~/.local/bin), symlink it instead of editing $PATH: ln -s "$(go env GOPATH)/bin/stagecoach" ~/.local/bin/stagecoach. make install overwrites the target in place, so the link stays valid across rebuilds.
Updating
Keep stagecoach current with one command — it detects how you installed it and delegates to that channel's own updater (Homebrew, Scoop, npm, mise, asdf, go install), prints the command where running it needs privileges (AUR) or is declarative (Nix), and self-swaps only for a direct (curl|sh / manual) install. A go install binary under ~/go/bin is detected automatically even when GOPATH is unset, so it delegates to go install …@latest rather than self-swapping. It never overwrites a package-manager-owned file, so it never fights your package manager. stagecoach upgrade is walled off from the commit core — it acquires no run lock, reads no repo, and invokes no provider.
stagecoach upgrade # detect → delegate (or self-swap), confirm, apply
stagecoach upgrade --check # exit 6 if an update is available, 0 if up to date (CI / cron)
stagecoach upgrade --rollback # one-step restore of the prior binary
See docs/cli.md#upgrade for the full flag reference.
Quick start
# 1. Stage your changes
git add feature/login.js
# 2. Run stagecoach — it snapshots, generates, and commits atomically
stagecoach
# [abc1234] feat: add login flow
# M src/login.js
# 3. Stage everything and commit in one step
stagecoach -a
# 4. Preview the real message (full pipeline: snapshot→generate→parse→dedupe→retry), no commit
stagecoach --dry-run
[!NOTE]
If generation fails, --dry-run exits 1 with a short message — not the full recovery recipe or exit 3/124 — since no commit was ever intended.
More options
stagecoach --push # commit + push after a clean run
stagecoach --edit # review in $EDITOR before the atomic commit
stagecoach --format conventional # force conventional-commit style
stagecoach --format conventional+body # conventional-commit subject PLUS a descriptive body
stagecoach --exclude '*.snap' # hide snapshot diffs from the model (still committed)
stagecoach --work-description "..." # lead with your description; model READs staged diffs on demand
See Features above and the CLI reference for the rest.
Multi-commit decomposition
https://github.com/user-attachments/assets/fbd9429d-99d2-4be9-99c8-cd01ac9c0504
With a dirty working tree and nothing staged, stagecoach automatically decomposes your changes into a sequence of logically-coherent commits using a four-role agent pipeline (planner → stager → message → arbiter). Each concept becomes its own commit. A start-of-run freeze (T_start) captures your entire change set up front, so files you change mid-run are excluded from every commit — the run only ever commits what existed when it started, and that holds across the leftover-reconciliation arbiter too (a concurrent edit can never sneak into a commit). As defense-in-depth, decompose also re-asserts its empty-index precondition at entry, so a stale trigger that reaches it with a staged index fails loudly rather than silently folding that hand-staged content into the run. The planner partitions changes per file and leans toward a soft count target, so a typical mixed tree lands at or below half the cap. The stager is constrained to staging operations: claude via a staging-only git allowlist (git add/apply/status/diff); pi instructionally (its task prompt) plus a HEAD-movement guard that aborts the run if the stager moves a ref. Either way, Stagecoach owns every commit via git plumbing.
# Auto-decompose — planner decides the count and grouping
stagecoach
# Decomposes into N commits automatically
# Use reasoning for deeper analysis on the planner
stagecoach --reasoning high
# Force exactly 3 commits
stagecoach --commits 3
# Keep the v1 single-commit behavior
stagecoach --single
# Route planning to a bigger model (per-repo .stagecoach.toml):
# [role.planner]
# provider = "claude"
# model = "opus"
# timeout = "600s" # per-role generation timeout; the planner defaults to 480s
[!NOTE]
--reasoning is provider-dependent: it engages deeper reasoning for pi (--thinking) and
claude (--effort). It is a no-op for agy and cursor — those two bake the reasoning
level into the model name (agy's (Low)/(Medium)/(High) suffix, cursor's -none/-low/-medium/-high
suffix), so the reasoning dial is the model you pick, not a flag. Other providers treat --reasoning
as a graceful no-op (no error) per. It applies to any role via --<role>-reasoning or
[role.*] reasoning.
Environment variables pass through to the agent. stagecoach always runs the provider CLI with
your shell environment intact (its own STAGECOACH_* vars layer on top), so anything the agent reads
works unchanged — e.g. point claude at a z.ai OpenAI-compatible endpoint:
ANTHROPIC_BASE_URL=https://… stagecoach --provider claude --model sonnet. The generated config
template also lists the STAGECOACH_* vars stagecoach itself reads.
See How Stagecoach works — Multi-commit decomposition for the pipeline architecture and CLI reference for all decompose and per-role flags.
lazygit & git alias
stagecoach integrate install lazygit # default key <c-a>; --key '<c-s>' to customize
stagecoach integrate install git-alias # enables `git stagecoach` everywhere
stagecoach integrate list # see what's installed / detected
Manual install (no stagecoach integrate)
If you prefer to paste the YAML yourself, add this to your lazygit config.yml (see docs/cli.md#lazygit-target for the canonical block):
customCommands:
- key: '<c-a>' # stagecoach-integration
context: 'files'
command: 'stagecoach'
loadingText: 'Generating commit message…'
output: 'none'
description: 'stagecoach: AI commit'
Stagecoach auto-detects which agents are installed and uses the first one it finds (in preference order: pi, opencode, cursor, agy, codex, claude). To see what's detected:
$ stagecoach providers list
NAME DETECTED DEFAULT
agy ✓
claude ✓
codex ✓
cursor ✓
opencode ✓
pi ✓ (default)
[!NOTE]
A provider whose command isn't on $PATH fails fast with exit 1 before any snapshot — no partial state, no rescue recipe.
Set a per-repo default with git config:
git config stagecoach.provider claude
# Optionally pin a model (single-backend providers use a bare model):
git config stagecoach.model sonnet
# For pi (multi-backend), prefix the model with the inference backend:
git config stagecoach.provider pi
git config stagecoach.model anthropic/claude-haiku
[!NOTE]
pi is a multi-backend provider: the inference backend is a slash-prefix on the model
(anthropic/claude-haiku). A bare model (no /) on pi is a config error. Set
git config stagecoach.model anthropic/claude-haiku (or [defaults] model = "anthropic/claude-haiku" in your config).
See Provider manifests for the full schema.
Or bootstrap a populated, working config (auto-detects your agent and writes per-role model defaults — for pi, the default, per-role models are left empty so you can supply your own inference-backend/model prefix; set model = "anthropic/claude-haiku" to pin a specific backend):
stagecoach config init
# Wrote config to ~/.config/stagecoach/config.toml
# See where that lives:
stagecoach config path
# ~/.config/stagecoach/config.toml
# Upgrade a v1 config to the current schema:
stagecoach config upgrade
[!NOTE]
The template also documents a [generation] section: output ("raw"|"json") and strip_code_fence are an opt-in override for how Stagecoach parses agent output. When unset, the per-provider [provider.<name>] value is used (defaulting to raw / true); set them under [generation] only to force the value across ALL providers.
Point discovery at a specific file with stagecoach --config path/to/config.toml. It is honored by every command — including the default commit action and the config init, config path, and config upgrade subcommands (e.g. stagecoach --config X config upgrade upgrades file X, and config path prints the resolved path) — so a provider declared under [provider.<name>] there is usable with --provider <name> directly. The path must exist: an explicit --config (or STAGECOACH_CONFIG) pointing at a missing file fails fast with exit 1 rather than silently falling back to auto-detection.
Config precedence (highest → lowest): CLI flags > STAGECOACH_* env vars > repo git config (stagecoach.*) > repo .stagecoach.toml > global config file > provider defaults > built-in defaults.
The snapshot workflow
Stagecoach creates commits against a frozen snapshot of your index — not the live state. This means you can keep staging more files while the message generates, and they will never be included in the current in-flight commit.
Pane A (lazygit / shell) Pane B (shell)
───────────────────────── ───────────────────────
git add feature/login.js
stagecoach ┐
↳ snapshotting… │ (user is free to work here)
↳ generating with pi… │ git add docs/login.md
↳ (10s pass) │ git add tests/login.test.js
↳ created abc1234 │ (these stay staged — NOT in abc1234)
┘
stagecoach # next run commits these
Generation time is no longer dead time. The in-flight commit only ever contains what was staged when it started, so you can stage the next batch freely while the current message generates — and a failed generation leaves your repo byte-for-byte unchanged.
Full CLI and config reference
The authoritative, always-available reference lives in the binary itself:
stagecoach --help # every flag, subcommand, and option
stagecoach config init # bootstraps a populated working config (auto-detects your agent)
stagecoach config upgrade # upgrades an existing config to the current schema version
stagecoach config path # shows where the global config lives
stagecoach lock status # inspect the per-repo run lock holder (path, pid, liveness, orphan status)
See the docs/ for the full reference (growing).
Adding a new agent
No recompilation needed — community agents land via a manifest in your config file. Drop a [provider.<name>] block into ~/.config/stagecoach/config.toml (or a repo-local .stagecoach.toml):
# ~/.config/stagecoach/config.toml
[provider.myagent]
command = "/opt/myagent/bin/agent"
prompt_delivery = "stdin" # stdin | positional | flag
print_flag = "--once"
model_flag = "--model"
default_model = "my-model-7b"
system_prompt_flag = "--system"
bare_flags = ["--no-mcp", "--ephemeral"]
output = "raw" # raw | json
Verify it works with providers show:
stagecoach providers show myagent
Then use it:
stagecoach --provider myagent
For field reference, copy from the shipped providers/*.toml files in this repo — providers/pi.toml is the cleanest template.
FAQ
Stagecoach is not for you if…
…you don't have (and don't want) a coding-agent CLI installed. Stagecoach has no model of its own — it is a thin wrapper around your agent. If you just want an API-key-based commit generator, opencommit is the right tool.
Will it corrupt my repo?
No. Stagecoach uses git write-tree + git commit-tree + git update-ref (atomic snapshot commits). A failed generation leaves the repo byte-for-byte unchanged — it never touches the live index during generation.
Safe to run twice. A per-repo run lock prevents two concurrent commit-producing runs from racing on HEAD. On the single-commit path (changes staged), an accidental double-invoke exits 0 if nothing new has been staged since the in-progress run began (nothing to do — an in-progress run already covers your staged changes), or exits 5 (Busy) if genuinely new work is staged (your changes stay staged to re-run). On the decompose path (nothing staged, dirty working tree), an accidental double-run exits 5 (Busy) rather than 0 — the in-progress run publishes a working-tree snapshot a contender can't reproduce without the lock, so it conservatively refuses and leaves your working tree untouched. (On a shared filesystem across hosts the lock can't help — the atomic update-ref CAS is the never-clobber-HEAD guarantee there.)
If the launcher closed without killing stagecoach — you closed the lazygit TUI, quit your IDE, or detached the terminal mid-run — the orphaned run self-exits via a parent-death watchdog and releases the lock, so it never strands. stagecoach lock status shows the holder's path and liveness so you can decide whether to kill/rm yourself; it never force-breaks a live lock.
And the commit-message call itself is chrome-less where the agent allows it — skills, extensions, context files, and MCP servers are switched off (pi, claude), so nothing loads, spawns, or injects around the call; providers that expose no such switch document the gap instead (docs/providers.md).
Does it send my code anywhere new?
No. It shells out to your agent under your existing auth and billing. On the commit path, stagecoach makes no network calls itself — it shells out to your agent, and your agent opens the connection exactly as it would if you ran it manually. The single exception is stagecoach upgrade (see below), which fetches only this project's own release artifacts and checksums.
Does stagecoach make network calls?
On the commit path (writing commit messages), no — stagecoach makes no network calls itself; it shells out to your agent, and your agent makes the connection. The one named exception is stagecoach upgrade (see Updating), which fetches only this project's own GitHub release artifacts and checksums — never provider credentials, never a diff, never your repo data. See docs/cli.md#upgrade.
Can it write multiple commits?
Yes. Run stagecoach with a dirty working tree and nothing staged, and it automatically decomposes the changes into a sequence of logically-coherent commits. Force a count with --commits 3, or keep the one-commit behavior with --single. See Multi-commit decomposition and the pipeline architecture.
How does it match my project's style?
It learns from the last 20 commits in your repo, with a prohibition on reusing their wording. It also guarantees that no generated subject duplicates one of the last 50 subjects. This means the messages improve over time as your project's style settles.
Which agents are supported?
Six built-ins are auto-detected: pi, opencode, cursor, agy (experimental), codex, claude. Any agent with a non-interactive CLI interface can be added via a [provider.<name>] manifest — see Adding a new agent.
End-to-end verification status (this build): all six providers — pi, opencode, cursor, agy, codex, and claude — have each been driven through a real commit-generation run.
How do I see what command it runs?
stagecoach --verbose
This prints the resolved provider command, raw agent output, and retry attempts.
Does it run my pre-commit hooks?
Yes. As of v2.4, the default stagecoach command runs your repository's standard commit hooks (pre-commit → prepare-commit-msg → commit-msg → post-commit) around every commit, scoped to the frozen snapshot — so atomicity and stage-while-generating are preserved (a pre-commit formatter's fixes are included; a hook that stages brand-new content aborts the run). --no-verify skips pre-commit and commit-msg only, mirroring git commit --no-verify. Hook mode (stagecoach hook install) remains for when you commit via plain git commit from an IDE — the two compose (covers stagecoach; hook mode covers git commit). See Commit hooks on the plumbing path.
What about PR generation, editor extensions, a GitHub Action, API-key providers?
Stagecoach writes commit messages — nothing else. Ideas we considered but deferred or rejected — VS Code/neovim extensions, a GitHub Action, API-key HTTP providers, generate-N-and-pick, diff chunking, and more — each with its reason. (Self-update shipped in v3.0 as stagecoach upgrade — see Updating.)
Contributing
Stagecoach is built with Go. To build from source:
make build # produces ./bin/stagecoach
make test # run the test suite
make help # see all targets
See providers/*.toml for the manifest format if you want to add support for a new agent.