Sync
Keep the shadow repo up to date with your public repo, and let Ghostbranch tidy up after merges.
Other people’s pull requests keep landing on public main while you work. ghostbranch sync brings them into the shadow repo, so your next feature starts from today’s code.
What sync does
Section titled “What sync does”It does three things, in order:
- Fast-forwards shadow
mainto publicmain. It never force-pushes and never resolves a conflict. If shadowmainhas commits publicmaindoesn’t, it stops withSYNC_DIVERGED(exit 4) and a person sorts it out. - Cancels the CI runs its own pushes started in the shadow repo (see below).
- Closes features whose pull request merged, if their branch hasn’t moved since they were published (see below).
Sync only reads the public repo. It never writes there.
Add --plan to see what it would push and close, without doing it:
When to sync
Section titled “When to sync”Run ghostbranch sync first, before you start a feature, because a new feature starts from shadow main. If you forget, feature start refuses and creates nothing:
Here public main had one merged pull request on it, which counts as two commits: the change and its merge commit. Other commands, such as status, only warn with SHADOW_BEHIND:
The gate is never affected: it always rebases your feature onto public main, not shadow main.
Cancelled CI runs
Section titled “Cancelled CI runs”Sync pushes to the shadow repo, and a push can start the shadow repo’s workflows that run on push. Those runs re-test code your public CI already tested, and they cost private Actions minutes. So sync cancels each run its pushes start, and lists them in its result. --no-cancel leaves them running.
To cancel runs, the token needs Actions write on the shadow repo. When sync can’t cancel a run, or can’t check, it warns CI_NOT_CANCELLED and names the runs it knows of. Nothing else goes wrong: they only cost minutes.
To stop those runs from starting at all, add a guard to each job that runs on push. ghostbranch audit suggests it, under PUSH_TRIGGERS_ON_SYNC:
jobs: test: # Runs on pushes to the public repo only, not on sync's pushes to the shadow repo. if: github.event_name != 'push' || github.repository == 'you/project'A job that already has an if: combines the two: if: <existing> && (github.event_name != 'push' || github.repository == 'you/project'). It changes nothing for your public CI.
Closing merged features
Section titled “Closing merged features”After your pull request merges, the next sync closes the feature for you. It deletes the feature’s branches and records in the shadow repo, closes the gate’s draft pull request, and says so. This sync followed the merge of pull request #23, add-search:
Its fast-forward of shadow main started one workflow run that runs on push, and sync cancelled it, as above. That sync also listed older features for a person to close, left out here.
Sync closes a feature on its own only when nothing would be lost: its pull request merged, and its branch hasn’t moved since it was published. Add --no-close to skip closing.
An archived feature has no shadow branch left, only its archive, refs/ghostbranch/archive/<name>. Once its pull request merges, sync closes it and deletes the archive too, as long as the archive holds exactly what was published. If it doesn’t, the feature reads merged_moved, and a person closes it.
Anything else is left for a person, and status names it with the command to run:
merged_moved: the pull request merged, but the feature has commits that weren’t published, or an archived feature’s archive doesn’t match what was published. Promote them, or close the feature.pr_closed: the pull request was closed without merging.branch_gone: the feature’s branch was deleted in the shadow repo.
ghostbranch feature close <name> deletes such a feature. It would lose work that was never published, so the person confirms it with a one-time code at their own terminal. An agent grant doesn’t cover it. The ledger keeps the SHA of every branch it deletes, so a branch can be pushed back. Add --plan to see what it would delete.
Merged stacks close from the bottom up. See Stacked features.
Scheduled sync
Section titled “Scheduled sync”By default you run sync yourself, and commands warn when shadow main falls behind. If you’d rather the shadow repo kept up on its own, turn on scheduled sync: a workflow in the shadow repo that runs sync on a timer.
- On the
ghostbranchbranch, setsync.schedulein.ghostbranch.ymlto a cron schedule, such as"23 */6 * * *"(every six hours). Push the branch to the shadow repo. - Run
ghostbranch upgrade --planto see the workflow it would add, thenghostbranch upgradeto add it.
Setting sync.schedule: "off" and running upgrade again removes it. ghostbranch status says whether it’s on.
Every six hours costs about 120 Actions minutes a month. If public commits change workflow files, the workflow needs a GHOSTBRANCH_SYNC_TOKEN secret in the shadow repo: a fine-grained token with Contents and Workflows write on the shadow repo only.
Upgrade
Section titled “Upgrade”ghostbranch upgrade updates the files Ghostbranch manages on the overlay branch (today, only the scheduled sync workflow) to match your installed Ghostbranch and your .ghostbranch.yml. It writes one commit to the ghostbranch branch, then runs the CI audit. It doesn’t update Ghostbranch itself: that’s a new install.
When a managed file is missing, out of date or edited by hand, commands warn OVERLAY_OUTDATED. Look first with --plan, which also warns when it would replace a hand edit:
Errors you may see
Section titled “Errors you may see”SHADOW_BEHINDexit 4Shadow main is behind public main, so feature start refused to start a feature from it; nothing was created.
Run ghostbranch sync --repo <public>, then start the feature again.
SHADOW_BEHINDwarningShadow main is behind public main.
Run ghostbranch sync --repo <public> (fast-forward only). The gate is unaffected: it always rebases on public main.
SYNC_DIVERGEDexit 4A shadow branch has commits that the public branch does not.
A person investigates the branches in details.branches; Ghostbranch never force-pushes a shadow branch.
This needs a person.
CI_NOT_CANCELLEDwarningSync pushed, but its pushes may have started workflow runs in the shadow repo that it could not cancel.
- the token lacks Actions write (or read) on the shadow repo
- GitHub's Actions API failed
Nothing is required: the runs only cost Actions minutes; cancel them on the shadow repo's Actions tab if you like. To let sync cancel them, give the token Actions write on the shadow repo. To stop them starting, add the guard that PUSH_TRIGGERS_ON_SYNC in ghostbranch audit suggests. ghostbranch sync --no-cancel skips cancelling. details.runs lists the runs sync knows of, each with its id and url; it is empty when sync couldn't list them.
PUSH_TRIGGERS_ON_SYNCwarningThese workflows run on pushes to synced branches. ghostbranch sync pushes with your token, and so does a scheduled sync that uses GHOSTBRANCH_SYNC_TOKEN, so each sync starts them in the shadow repo, re-testing code public CI already tested, on private Actions minutes.
ghostbranch sync cancels the runs its pushes start when the token has Actions write on the shadow repo. To stop them for good, a person adds the guard in details.guard to those workflows' push-triggered jobs, for example if: github.event_name != 'push' || github.repository == 'org/project'. A job that already has an if: combines them: if: <existing> && (github.event_name != 'push' || github.repository == 'org/project'). Once every push-triggered job carries it, the audit stops reporting the file and sync stops looking for its runs. It changes nothing in the public repo's own CI. Or accept the minutes. A scheduled sync pushed with the workflow's default token starts no workflows.
FEATURE_NOT_CLOSEDwarningSync didn't close a merged feature: the close failed, sync couldn't survey the features and so closed none, another feature's record couldn't be read, so sync left the merged features open, or its PR merged into the public branch below it, not main.
If details.deleted is set, the feature is gone: close its gate draft PR in the shadow repo by hand. With details.reason merged_into_parent_branch, its PR merged into public branch details.base, not main: a person checks that branch holds its changes, then closes it (ghostbranch feature close <name>); sync doesn't. With details.unreadable, the features named there have records that can't be read, and any of them may be stacked on this one: a person closes each at the console (ghostbranch feature close <name>) or fixes its record, and the next sync closes this feature. Otherwise run ghostbranch feature show <name>, then feature close <name>. With no details.feature, sync couldn't survey the features and closed none: run ghostbranch status to see why.
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.