Skip to content
GhostbranchdocsGitHub

Working on two machines

Use one repo pair from a laptop and a desktop, and know what each machine keeps to itself.

Your features live in the shadow repo, so any machine with access to it can pick them up. A few things stay on the machine where they were made, though, and that decides how you hand work from one machine to the other.

In the shadow repo: both machines see it On each machine: never shared
Your feature branches, and each feature’s record Your GitHub login
Each gate’s result, and its job while CI runs Agent grants (agents allow)
The overlay branch and .ghostbranch.yml Waivers (waivers add)
A reference to each plan The plan files that apply reads
The ledger that ghostbranch log reads
The push guard, which belongs to one checkout

So the left column follows you, and the right column you set up again on each machine.

The first machine created the shadow repo. The second one adopts it:

  1. Install the same version of Ghostbranch as on the first machine (see below).

  2. Log in.

  3. Clone the public repo, and add the shadow repo as a remote called shadow:

    Terminal window
    git clone https://github.com/you/project.git
    cd project
    git remote add shadow https://github.com/you/project-shadow.git
  4. Adopt the pair. Look first:

    Terminal window
    ghostbranch init you/project --adopt --plan
    ghostbranch init you/project --adopt

    Adopting registers the pair on this machine and checks the shadow repo. It doesn’t create anything that’s already there.

  5. At a terminal, init asks whether to install the push guard in this clone, then runs ghostbranch doctor. If you said no to the guard, or passed --no-guard, run ghostbranch guard install here later.

Read the plan before you adopt. If your public repo keeps the branches of merged pull requests, adopt copies them into the shadow repo as well:

Terminal
ghostbranch init you/project --adopt --plan
Dry run; nothing was changed. This would:
- create refs/heads/add-search in you/project-shadow at 7cd4d7517f3f
- create refs/heads/fix-typo in you/project-shadow at faf0688f63a9
- create refs/heads/search-filters in you/project-shadow at db882ee3964e
only the branch lines shown

This run was on a machine that already had the pair, so it shows only the branch lines. On a new machine the plan also registers the pair and sets up Ghostbranch’s local copy of the shadow repo. Those branches are copies of what’s already public, so nothing leaks. But they carry the names of features that sync has already closed, so the shadow repo shows those branches again. If you’d rather not have them, delete them in the shadow repo after you adopt.

A grant you made on one machine doesn’t cover the other. Check with agents status on each:

Terminal
ghostbranch agents status
a warning about agent sessions left out
you/project: agents may publish for 157h14m43s more
The app token from login can publish to you/project: true

If you want agents to publish without you on the second machine too, run ghostbranch agents allow there, at your own terminal. See Promote and approval.

Waivers work the same way. A finding you waived on one machine still blocks a publish from the other. Waive it again there, or publish from the machine that has the waiver.

Commit and push to the shadow repo before you leave the first machine. Then, on the second one:

Terminal window
ghostbranch sync
git fetch shadow
git switch add-search
git pull
ghostbranch status

status shows the feature as the first machine left it. If a gate was still waiting for CI, status names its job under gate.job in --json output, and ghostbranch wait <job> finishes it here. Long CI explains jobs.

Then commit, push, gate and promote from this machine as usual.

A plan is a file on the machine that ran promote. The other machine can’t apply it. apply stops with PLAN_NOT_FOUND, and looks in the shadow repo to tell you why.

When the plan was made on another machine, the shadow repo knows it, and apply says so (error.details.reason is other_machine):

Terminal
the message's format, with a sample plan ID:
ghostbranch apply 20261013T040000Z-0a0b0c
error: plan 20261013T040000Z-0a0b0c was made on another machine (the shadow repo has its ref, but plan files stay where they were made): apply it there, or make a new plan here [PLAN_NOT_FOUND]

Either apply it on the machine that made it, or make a new plan here with ghostbranch promote add-search --plan. A new plan is safe: it’s checked against the repos as they are now. When exactly one feature’s gate matches the plan, next gives that promote for you.

When neither this machine nor the shadow repo knows the plan, the reason is unknown. It’s usually a typo in the ID. This run used a made-up one:

Terminal
ghostbranch apply 20990101T000000Z-abcdef
error: no plan 20990101T000000Z-abcdef on this machine or in you/project-shadow [PLAN_NOT_FOUND]
Next:
ghostbranch status --repo you/project
see the features, then plan the one you meant again with promote <feature> --plan

The same goes for an apply that stopped after its push: resume it on the machine that made the plan. Updating an open PR covers the one case where that matters.

Run ghostbranch version on both machines, and keep them the same.

Each record in the shadow repo says which version of Ghostbranch wrote it. When a newer Ghostbranch writes a record in a newer format, the older one on your other machine can still read it, but it won’t change it. Reads warn:

Terminal
ghostbranch status
warning: feature add-search's record was written by a newer Ghostbranch (schema 0.13); it is shown as far as this binary can read it, and won't be changed
then the status table

Anything that would change the record stops:

Terminal
ghostbranch gate add-search
error: this feature record was written by a newer Ghostbranch (schema 0.13; this binary knows 0.12); upgrade ghostbranch before changing it [UPGRADE_NEEDED]

These two are the messages Ghostbranch prints, shown with sample values, not captured runs. The fix is the same for both: upgrade Ghostbranch on the older machine. Never edit the record to make the older version accept it.

UPGRADE_NEEDED also appears when .ghostbranch.yml has a key the older version doesn’t know, because the newer one added it. A merged feature whose record is newer stays open while you sync from the older machine. Sync from the newer one, or upgrade the older one, and it closes.

PLAN_NOT_FOUNDexit 2No plan file with this ID (error.details.plan) is on this machine (error.details.reason; error.details.public names the pair looked in, when one was found).
Causes
  • other_machine: the plan was made on another machine, or in another workspace or GHOSTBRANCH_HOME with the same shadow repo; the shadow repo has its ref, but plan files stay where they were made
  • unknown: no such plan here or in the shadow repo (a typo, or a plan for another shadow repo)
What to do

For other_machine, apply it on the machine that made it, or make a new plan here: next gives promote <error.details.feature> --plan when one feature's gate matches the plan. Otherwise run ghostbranch status and promote the feature you meant again.

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).
What to do

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.

RECORD_NEWERwarningA record was written by a newer Ghostbranch; this binary shows what it can read of it and won't change it.
What to do

Upgrade ghostbranch on this machine (the other machine is probably newer). Reads still work; commands that would change the record stop with UPGRADE_NEEDED.

SHADOW_NOT_EMPTYexit 4The repo given to init --adopt has unrelated content.
What to do

A person picks an empty repo or a pure mirror of the public repo.

This needs a person.

JOB_STALEexit 4A newer gate replaced this job: the feature, public main or the overlay changed, and the feature was gated again.
What to do

Wait on the current job (error.details.current_job), or, if there is none, gate the feature again.