The repo pair and the shadow
How your public repo and its private shadow repo fit together, and what moves between them.
Ghostbranch never keeps your private work in the public repo, not even for a moment. It all lives in a second repo, and only a gated publish carries it across. The examples use a public repo called you/project.
Two repos
Section titled “Two repos”- The public repo is your repo, exactly as it is today. Ghostbranch only ever writes to it through
promote, which opens a pull request. - The shadow repo is a private repo that
ghostbranch initcreates next to it, calledyou/project-shadowunless you name it. It holds:- a copy of public
main, whichsynckeeps up to date; - your feature branches;
- a gate branch for each gated feature (
ghostbranch-gate/<feature>), with a draft pull request where CI runs; - the overlay branch,
ghostbranch, which is the shadow repo’s default branch; - a small record for each feature: its parent, its last gate and its public pull request.
- a copy of public
Your checkout is a clone of the public repo with two remotes: origin for the public repo and shadow for the shadow repo. The first feature start prints the line that adds the shadow remote.
What moves where
Section titled “What moves where”| From | To | How |
|---|---|---|
Public main |
Shadow main |
ghostbranch sync, fast-forward only |
| Your checkout | A feature branch in the shadow repo | git push shadow <feature> |
| A feature branch | A gate branch and a draft PR in the shadow repo | ghostbranch gate |
| A gated feature | A pull request on the public repo | ghostbranch promote, then a person’s apply |
| A merged pull request | Shadow main, and the feature closes |
ghostbranch sync again |
Promote is the only arrow that points at the public repo. Nothing else Ghostbranch does writes there, and the push guard stops git push from sending feature work there by mistake.
The overlay branch
Section titled “The overlay branch”The ghostbranch branch holds what belongs to the pair and never to the public repo:
.ghostbranch.yml, the pair’s settings;.ghostbranch/, for your own internal notes and docs;.github/workflows/ghostbranch-*.yml, private CI workflows, such as the optional scheduled sync.
When a feature is gated, these files are added on top of it, so CI sees them. None of them is ever promoted. This is a real .ghostbranch.yml, as init writes it:
# Ghostbranch configuration. It lives only on this shadow repo's# "ghostbranch" branch and is never promoted.version: 1public: you/projectshadow: you/project-shadow
promote: target: pr # preserve publishes each feature commit; squash publishes one commit; # curated regroups, reorders and rewords commits from a --curate file. default_mode: preserve # noreply publishes every commit as your GitHub noreply identity; keep # publishes the original authors; map uses author_map. author_email: noreply
leak_scan: strip: [".ghostbranch/**"] markers: begin: BEGIN-INTERNAL end: END-INTERNAL block_patterns: [] large_file: 5MB secrets: true
sync: # Scheduled sync is off: run ghostbranch sync yourself (commands warn when # shadow main falls behind public). To opt in, set a cron such as # "23 */6 * * *" (about 120 Actions minutes a month), then run # ghostbranch upgrade. schedule: "off" branches: [main]
ci: rewrite_repo_guards: rewrite
gate: ci_appear_timeout: 10m ci_timeout: 60mEach section has its own page: promote in History modes, leak_scan and ci in The gate, sync in Sync, and the gate timeouts in Long CI.
To change a setting, edit the file on the ghostbranch branch and push that branch to the shadow repo. A key Ghostbranch doesn’t know, even under sync: or ci:, is an error, so a typo can’t be silently ignored.
Seeing the pair
Section titled “Seeing the pair”ghostbranch status shows the pair’s health on one line, then every feature:
- The first line says whether shadow
mainhas fallen behind publicmain(runsync), whether the overlay’s managed files need anupgrade, and whether scheduled sync is on. - The tree shows stacked features:
search-filtersis built onadd-search, andsearch-uionsearch-filters. - DRIFT says whether the feature still merges cleanly onto public
main. - GATE is the feature’s last gate, and PR its public pull request, if it has one.
The states you’ll see most:
| State | Means |
|---|---|
draft |
Not gated yet. |
in_review |
Gated, but the gate is still running, failed, or is out of date. |
gate_passed |
The last gate passed and still matches the feature. Ready to promote. |
published |
Its pull request is open on the public repo. |
pr_behind |
The pull request is open, but the feature has commits it doesn’t have yet. See Updating an open PR. |
archived |
Published and handed off: its pull request takes fixes directly, and its shadow branch is archived. See After publishing. |
merged |
The pull request merged. The next sync closes the feature. |
parent_stale |
A stacked feature whose parent moved since it was started or restacked. See Restack. |
public_pr_needs_update |
Its open pull request still carries the commits of a merged one below it. See After the merge. |
abandoned |
Parked with feature abandon. |
A few more need a person: merged_moved and pr_closed (see Sync), branch_gone, and unreadable, a feature record Ghostbranch can’t read.
ghostbranch feature list prints the same table without the first line, and leaves out abandoned features unless you add --all. ghostbranch feature show <name> prints everything about one feature:
All three only read. They push nothing and write nothing to the ledger, so run them as often as you like. Add --json for the same data in a form a script can read.
Errors you may see
Section titled “Errors you may see”OVERLAY_OUTDATEDwarningA Ghostbranch-managed file on the overlay branch is missing, out of date, or edited by hand.
Run ghostbranch upgrade --plan to see the change, then ghostbranch upgrade.
UPGRADE_NEEDEDexit 1Something this pair uses was written by a newer Ghostbranch: a record (error.details.record: feature, plan, grants or waivers) or the overlay config (error.details.unknown_key, a key this binary doesn't know).
Upgrade ghostbranch on this machine to the version that wrote it, or newer (the other machine is probably newer). Don't edit the record or the overlay config to make an older binary accept it. If the key in error.details.unknown_key is a typo, a person fixes it in .ghostbranch.yml on the overlay branch.
NOT_A_PAIR_CHECKOUTexit 2This directory is not a checkout of a registered public repo.
Run the command inside a clone of the public repo, after ghostbranch init.
SHADOW_NOT_PRIVATEexit 1The shadow repo is public or a fork, so Ghostbranch refuses to touch it.
- someone changed the shadow repo's visibility
A person makes the shadow repo private (and not a fork) on GitHub.
This needs a person.