Create, use, and delete disposable PGRun Postgres
branches — from the command line or as an MCP tool. One static Go binary,
stdlib only, no external dependencies.
A branch is a real, connectable Postgres: copy-on-write off a project's base
branch, ready in about the time a normal database takes to boot, gone the
moment you delete it or its TTL expires.
Create, use, and delete disposable PGRun Postgres branches — the pgrun CLI + MCP server for coding agents
The agent-first path — three commands, then hand the work to Claude Code:
pgrun auth login # opens your browser to authorize; --paste to paste a token instead
pgrun skill install # installs the pgrun-branching skill into ~/.claude/skills
claude # then: "Add an index to users.email and test it on a pgrun branch"
The skill teaches Claude when to create a branch, how to inject its
DATABASE_URL into only the commands that need it, and when to delete it.
Claude does the branching; you never create a branch by hand.
Prefer the CLI directly?
pgrun auth login # opens your browser to authorize; --paste to paste a token instead
pgrun branch create myproject --name scratch --ttl 1h --wait
# DATABASE_URL=postgres://... on stdout, only on ready (exit 0)
psql "$DATABASE_URL" -c 'select 1'
pgrun branch delete myproject scratch
Or, for the common "run one command against a fresh, disposable branch"
case — the primitive an agent reaches for most — one line:
Creates the branch, waits for it, runs the command with DATABASE_URL set
in its environment, deletes the branch afterward (even if the command
fails), and exits with the command's own exit code.
First time? Connect your production database
Branches above fork whatever base a project already has. To branch off a
privacy-safe copy of your real production database instead, connect it
once — Connect Postgres → Protect Data → Safe Copy — then branch as usual:
pgrun auth login
pgrun project use production # alias: pgrun projects use
pgrun source add --name production --url "$DATABASE_URL" # omit --url for a hidden prompt
pgrun source status production # poll until connected
pgrun source protect production # prints the review table, decides nothing
pgrun source protect production --set public.users.email=fake --approve
pgrun source copy production --wait # the one-time Safe Copy
pgrun branch create production --name dev --wait # branch off the Safe Copy
source protect never activates anything on its own — it prints a table of
every column, resolve the ones marked UNRESOLVED with --set (add
--acknowledge <table>.<column> to copy a sensitive one unmasked), then
--approve yourself once you're satisfied. See Sources below
for the full command table and how secrets are handled.
Install
curl -fsSL https://pgrun.dev/install | sh
The installer downloads the matching release binary from
github.com/pgrundev/pgrun-cli and
verifies its SHA-256. Or install from source with
go install github.com/pgrundev/pgrun-cli/cmd/pgrun@latest, or build directly:
git clone https://github.com/pgrundev/pgrun-cli
cd pgrun-cli && go build -o pgrun ./cmd/pgrun
(brew install pgrundev/tap/pgrun and npx @pgrun/cli are wired in goreleaser
but gated behind tap/npm credentials — curl|sh is the supported beta path.)
Auth
pgrun auth login opens your browser to app.pgrun.dev; click
Authorize and the CLI receives a token minted just for it (named "pgrun
CLI on <hostname>", revocable any time from the Tokens page) — it's never
shown to you. Over SSH, or with --no-browser, it prints a URL and code to
open on any device instead. It never asks for an API URL: --url, then
PGRUN_API_URL, then the saved config, then https://app.pgrun.dev.
--paste keeps the older flow — a direct link to the Tokens page, paste a
token, verified against the API before it's saved. pgrun auth logout
clears the saved config. pgrun auth status prints the URL and a
6-character token fingerprint — never the token.
pgrun auth login
For scripts and CI, skip the browser: set PGRUN_API_TOKEN/PGRUN_API_URL
in the environment, or pgrun auth set --token <TOKEN> [--url <URL>] writes
~/.config/pgrun/config.json at mode 0600 directly (no prompt, no network
call). Precedence (highest first): --url/--token flags on any command →
env → the config file.
Every source subcommand overrides the API base with --api-url instead
of --url — on source add/source update, --url is already the
production connection URL. See Sources.
Create a branch. --engine picks the runtime: pgx (PGX Beta, ready in under a second; a 422 engine_unavailable if the project cannot run it, never a silent fallback) or postgres; omit it for the server's choice (PGX Beta when eligible, else PostgreSQL). --wait polls every 2s until ready/failed/timeout; prints DATABASE_URL=... on ready (--json adds a database_url field). Without --wait, prints a status line and exits 0 immediately.
pgrun branch list <project> [--json]
Table: NAME STATUS BASE PARENT VERSION CREATED EXPIRES. Never shows a connection string.
pgrun branch get <project> <name> [--json] (alias status)
One branch's status. Human mode never shows connection_url.
pgrun branch url <project> <name>
DATABASE_URL=... if ready and credentialed (exit 0), else a sanitized reason (exit 1).
pgrun branch env <project> <name> [--format=json]
export DATABASE_URL="..." (eval-able), or {"database_url":"..."} with --format=json. Same readiness rule as branch url.
Create a fresh branch, wait for it, run the command, and (--delete-after) delete it afterward — even if the command fails or the job is cancelled (SIGTERM/SIGHUP are forwarded to the command, then the branch is deleted). --timings prints one pgrun: timings branch=… engine=… database=… create_ms=… ready_wait_ms=… command_ms=… delete_ms=… total_ms=… exit=… line to stderr; it never prints the URL.
Mint the branch from a CI template instead of the full sanitized copy: the schema (plus migration bookkeeping rows such as schema_migrations) and the template's seed SQL, no application rows. --schema-only uses the built-in schema-only template, defining and building it on first use. A stale template (schema or seed changed) is rebuilt first.
Define (idempotently) a CI template and build it once; later calls are no-ops until the source schema or the seed file changes. template list/get/build/delete manage it.
pgrun branch delete <project> <name> [--json]
202 → deleting (exit 0); 409 on the base branch or a branch with children (exit 1).
Authorize in your browser (default); --paste a token instead.
pgrun auth logout
Remove the saved config.
pgrun auth set --token <t> [--url <u>]
Advanced/CI: write the config file directly, no prompt.
pgrun auth status
Print URL + token fingerprint.
pgrun skill install [--project | --dir <dir>]
Install the embedded pgrun-branching skill for Claude Code (~/.claude/skills/pgrun-branching/SKILL.md by default; --project → ./.claude/skills; --dir → any other skills root). Idempotent; overwrites a stale copy.
pgrun skill status [--project | --dir <dir>]
Exit 0 only if the skill is installed and matches this binary's copy.
pgrun skill show
Print the embedded skill to stdout.
pgrun mcp serve
Run as an MCP server over stdio (see below).
pgrun version
Print the version.
--json on create/list/get/delete prints the API's response bytes
verbatim — that's the machine-readable contract, not the human text.
(branch create --wait --json's ready response is the one exception: it
adds database_url and "ready": true on top of the raw body, so an agent
scripting against --json never needs a second call just to learn the
connection string:
0 success (or, for branch exec, the exec'd command's own exit code) ·
1 operation/API failure · 2 auth/config missing or rejected (with a
pgrun auth login hint; pgrun auth login itself exits 2 when CI is
set — it refuses to open a browser, so use PGRUN_API_TOKEN or auth set
instead) · 64 usage (bad flags/args) · 130 login cancelled with Ctrl+C.
Sources
Connect a real production database, resolve its data-protection policy,
then create a one-time privacy-safe copy to branch from — Connect Postgres
→ Protect Data → Safe Copy:
Command
What it does
pgrun source list [--json]
Table: NAME STATUS PROTECTION SAFE COPY BRANCHES. Exit 0 on any successful read.
pgrun source get [<name>] [--json]
One key=value summary line plus the next command to run. Exit 0 on any successful read.
pgrun source status [<name>] [--json]
The connect → schema → protect → Safe Copy checklist, plus Next:. Exits 1 when the source is failed, action_required, or its last connection check failed; 0 otherwise.
Connects a new Production Database. Omit --url for a hidden prompt (Connection URL (input hidden): , echo off on a terminal); --json requires --url — a prompt would corrupt the JSON stream. --wait polls the connection check to settled (exit 0 once connected, 1 on a failed check or timeout).
Replaces the connection URL. The server accepts a new one while the source is still connect (no URL yet) or checking — which includes a check that failed — and answers 409 once it is connected. Same secret handling and exit codes as add.
Reviews the data-protection policy — prints the table, activates nothing by itself. --approve activates it and is refused (422) while any column is unresolved; --review asks pgrun to flag the unresolved columns. A repeated --set key is a usage error; there is no "copy everything" shortcut. Exit 1 while columns are unresolved, or (only the status read, no protect request) when the source is not connected yet; 0 once the review is complete or the policy is activated. --review always exits 0 — asking for a review is not itself a failure — the one exception to "1 while unresolved". --approve that comes back without an active policy exits 1.
Creates the Safe Copy. Refuses (exit 1, only the status read) when the source reads action_required because production's schema changed since the policy was approved; if the status read merely flags a schema change while the server still says ready_to_copy, it prints ! Production schema changed and lets the server decide, a Safe Copy already exists, the last one failed, or protection isn't active yet. --wait polls to ready (exit 0) or failed (exit 1).
[<name>] is the Production Database (= project) name — sources are
projects — and falls back to .pgrun/project exactly like a branch
command's [<project>]; set it once with pgrun project use <slug>
(alias: pgrun projects use).
Secrets. On add/update, --urlis the production connection URL
(both schemes are accepted — postgres:// and postgresql://): never
printed, never included in --json, never repeated in an error, sent only
in the request body's connection_url field. Prefer the hidden prompt
(pgrun source add --name production, no --url) over typing the URL on a
shared shell — a flag's value is visible to other local processes and lands
in shell history, the prompt is not. Never assign the URL to a non-string
flag (--wait=…, --json=…, --timeout=…) — Go's flag package echoes a
value it cannot parse verbatim, which would print the secret; always pass it
to --url on its own.
Because --url is taken as the connection URL on add/update, the
API-base override on every source subcommand (including those two) is
--api-url, not --url (--token is unchanged). Every other command
family — branch, project, auth — keeps --url as the API base.
A Safe Copy is a one-time copy in this release — refresh/continuous sync
(Phase 6) does not exist yet; sync always reads snapshot_only.
MCP — use pgrun as an agent tool
pgrun mcp serve speaks the Model Context Protocol
over stdio (hand-rolled JSON-RPC 2.0, line-delimited, no SDK) so an agent can
create/list/get/delete branches as tools instead of shelling out. Config is
env-only — an MCP host launches pgrun as a subprocess and passes
credentials through its own env, not flags or the config file:
(Drop that under mcpServers in Claude Desktop's config, or in a project's
.mcp.json for Claude Code.)
Tools, one per CLI branch subcommand (no source tools yet — connect,
protect, and copy a Production Database from the CLI):
pgrun_create_branch{project,name,ttl?,parent?,wait?=true,timeout_seconds?=300}
— waits for ready/failed by default; result JSON includes
connection_url, a database_url alias, and "ready": true once ready,
so one call is enough to start using the branch.
pgrun_list_branches{project}
pgrun_get_branch{project,name}
pgrun_delete_branch{project,name}
Results are content:[{type:"text",text:<api-json>}]; failures come back as
isError:true with a sanitized message — never a crash, never the token.
Pair it with the skill — MCP/the CLI give an agent the tools, the
pgrun-branching skill gives it the
playbook (when to branch vs. use local Postgres, poll semantics, cleanup
rules, what each error means). pgrun skill install drops it into
~/.claude/skills — the same file is embedded in the binary, so the
installed copy always matches the commands this version understands. See AGENTS.md for the full
agent-facing contract, including the safety rules around production DSNs.
Development
Stdlib only — no external dependencies, offline-buildable.
make build # bin/pgrun
make test # go test ./...
make vet # go vet ./...
make fmt # gofmt -w
Go 1.22+. go test ./..., go vet ./..., and gofmt -l . are expected to
stay clean at all times.