Common problems
The errors people hit most, what each one means, and how to fix it.
Every error Ghostbranch prints ends with its code in brackets, such as [LOCKED]. ghostbranch explain <code> says what the code means and what to do, with no token and no network. These are the codes people run into most.
If yours isn’t here, run ghostbranch doctor: it checks your whole setup and names each fix. Error and warning codes lists every code.
PAIR_NOT_FOUND: this repo isn’t set up here
Section titled “PAIR_NOT_FOUND: this repo isn’t set up here”You named a repo, or ran a command in its clone, and it isn’t set up on this machine. It happens on a second machine, or with a different GHOSTBRANCH_HOME. Next: offers both ways to set it up:
- The shadow repo already exists, because you set it up on another machine: adopt it with
init --adopt. This is almost always the one. - There’s no shadow repo yet:
initwithout--adoptcreates one. - A typo in
--repo: check the name.error.details.publicnames the repo it looked for.
Read the plan each --plan prints, then run the same command without --plan.
PAIR_AMBIGUOUS: which repo?
Section titled “PAIR_AMBIGUOUS: which repo?”Ghostbranch works on one pair of repos at a time, and can’t tell which one you mean. Outside a clone of the public repo, with more than one pair set up on this machine, you see:
With --json, error.details.candidates lists the pairs set up here. Run the command inside a clone of the public repo, or name the repo:
The same code comes in a folder that is a git checkout, but not of a GitHub repo. A repo that isn’t set up on this machine at all is PAIR_NOT_FOUND instead.
NO_CI: the gate saw no checks
Section titled “NO_CI: the gate saw no checks”The gate opens a pull request in the shadow repo and waits for its CI. If no check appears within 10 minutes, it stops with exit code 3. The message has this form (here with the values of a real run):
error.details.reason says which case it is:
no_workflow: the code the gate tested has no workflow under.github/workflowsat all, so nothing could start, and the message says so. Add CI to the public repo. If your CI is an outside app rather than GitHub Actions, install that app on the shadow repo too.actions_disabled_or_untriggered: there are workflows, but none ran. Either Actions is turned off in the shadow repo (turn it on in its settings, under Actions), or no workflow runs onpull_request: add that trigger to one.- No reason: the gate couldn’t read its candidate, so it can’t tell which. Check both.
Then gate again. If your CI is just slow to start, raise gate.ci_appear_timeout in .ghostbranch.yml.
TOKEN_PERMISSION_MISSING: the token can’t do this
Section titled “TOKEN_PERMISSION_MISSING: the token can’t do this”GitHub refused something because the token lacks a permission. The error names the permission, the repo and the command:
Run ghostbranch doctor: its token check lists everything the token lacks, for each group of commands, so you can fix them all at once. Then grant them:
- With the Ghostbranch app, check that the app’s installation includes both repos.
ghostbranch loginprints its install page. - With a fine-grained token, add the permissions in the token’s settings on GitHub. Tokens and the keychain lists what each command needs.
Grant only what doctor names. An agent’s token in particular should stay unable to publish.
LOCKED: another command is running
Section titled “LOCKED: another command is running”Ghostbranch runs one command that changes things at a time on each pair, on each machine. When another one holds the pair, you see which one:
A gate in another terminal held the lock. That run was on Windows, so the lock file’s path is shown as it would be on Linux. With --json, error.details.held_by has the same three parts: the command, its process ID and when it started.
LOCKED is retryable (error.retryable is true): wait a few seconds, then run the same command again, as next says. The other command may be in another terminal, or one a coding agent is running. When the lock was busy as a gate job tried to record its result, next is wait on that job instead.
A gate holds the lock while it prepares and pushes its candidate, and again for a moment when CI finishes, to record the result. While it waits on CI, other commands run as usual.
The lock frees itself when that command ends, even if it crashed or you closed its window. There’s no lock file to delete.
SHADOW_BEHIND: sync first
Section titled “SHADOW_BEHIND: sync first”Public main has moved on, and the shadow repo hasn’t caught up. feature start refuses to start a feature on old code; other commands only warn. Run ghostbranch sync, then start the feature again. Sync shows the message and when to sync.
PLAN_STALE: make a new plan
Section titled “PLAN_STALE: make a new plan”Something moved between promote --plan and apply: public main, the feature, the overlay branch or the gate. apply published nothing. Make a new plan with the command in Next:, and apply that one. When the plan is stale shows a real run.
PLAN_NOT_FOUND: no such plan here
Section titled “PLAN_NOT_FOUND: no such plan here”apply was given a plan ID, but there’s no plan file with that ID on this machine. apply looks in the shadow repo to tell you why, and error.details.reason says what it found:
other_machine: the plan was made on another machine, or under anotherGHOSTBRANCH_HOME. Plan files stay where they were made. Apply it there, or make a new plan here. See Plans stay where they were made.unknown: neither this machine nor the shadow repo knows it. It’s usually a typo in the ID:
An ID that isn’t in the plan ID format at all is BAD_FLAG, not this.
NO_OPEN_PR: feature pull needs an open pull request
Section titled “NO_OPEN_PR: feature pull needs an open pull request”feature pull brings commits made on GitHub into your feature. It needs the feature’s open pull request. When there isn’t one, it stops, and error.details.reason says why. Here the feature was never published:
The reasons, and what next offers for each:
not_published: publish it first, withpromote --plan.merged: its pull request merged, andsynccloses the feature.closedorbranch_gone: its pull request was closed without merging, or its public branch is gone. A person decides whether to promote it again, as a new pull request, or close the feature.
feature archive needs an open pull request too, and stops the same way.
FEATURE_ARCHIVED: the feature was handed off
Section titled “FEATURE_ARCHIVED: the feature was handed off”After publishing, apply archives the feature unless you chose Keep. Its pull request is then the feature’s home, so the commands that work through the shadow repo stop with FEATURE_ARCHIVED: promote, gate, feature pull and waivers add. So do feature start --from and feature restack --onto, because nothing can be built on an archived feature: start from main, or restack onto main, instead.
To change its pull request, push to the pull request’s branch. next gives the steps, with the branch from error.details.branch:
git fetch origin add-searchgit switch --detach origin/add-search# commit your fix heregit push origin HEAD:add-searchCommit on origin/add-search, never on the old shadow branch: the pre-push guard refuses its private commits. Promote and approval explains Archive and Keep.
UPGRADE_NEEDED: a newer Ghostbranch wrote this
Section titled “UPGRADE_NEEDED: a newer Ghostbranch wrote this”A record or the settings were written by a newer version of Ghostbranch than the one you’re running. It happens most when you work on two machines and upgraded only one. Upgrade this machine’s copy: see Upgrading.
When error.details.unknown_key is set, it’s a key in .ghostbranch.yml this version doesn’t know. If no newer version wrote it, it’s a typo, or a section left empty: fix it on the overlay branch (see Keys Ghostbranch doesn’t know). Don’t edit records to make an older version accept them.
no_console in Git Bash on Windows
Section titled “no_console in Git Bash on Windows”Publishing asks you to type a code at a console, and ghostbranch login needs one too. Git Bash’s window (mintty) connects programs through pipes, not a Windows console, so Ghostbranch finds no console there and stops:
error.details.reason is no_console. Nothing was published, and the plan is still saved. Run the same command in PowerShell, Command Prompt or Windows Terminal, and type the code there. ghostbranch login stops the same way in Git Bash, with LOGIN_UNAVAILABLE: log in from one of those too, or pipe a token to ghostbranch login --with-token, as the error says.
You can keep using Git Bash for git and for the commands that don’t ask you anything.
Errors you may see
Section titled “Errors you may see”PAIR_NOT_FOUNDexit 2This repo isn't set up on this machine (in this workspace and GHOSTBRANCH_HOME); error.details.public names it.
- it was set up on another machine, or under another GHOSTBRANCH_HOME or workspace
- it was never set up
- a typo in --repo
When its shadow repo already exists, run ghostbranch init <public> --adopt --plan (it only registers the pair here and checks the shadow repo); otherwise ghostbranch init <public> --plan creates one. next lists both, adopt first.
PAIR_AMBIGUOUSexit 2Ghostbranch cannot tell which repo pair to use.
Pass --repo owner/name, or --repo . inside a checkout of the public repo.
NO_CIexit 3No CI checks appeared on the gate candidate (error.details.reason).
- no_workflow: the candidate has no workflow under .github/workflows, and no CI app posts checks
- actions_disabled_or_untriggered: Actions is disabled in the shadow repo, or no workflow triggers on pull_request
- no reason: the gate candidate couldn't be read, so which of the two holds is unknown
For no_workflow, add CI to the public repo, or, if CI is an external app, install it on the shadow repo. Otherwise enable Actions in the shadow repo or add a pull_request trigger. Then gate again.
TOKEN_PERMISSION_MISSINGexit 1The token in use lacks a GitHub permission this command needs: error.details.permission on error.details.repo.
- a fine-grained token or the Ghostbranch app wasn't granted that permission, for example Workflows: write to change .github/workflows files
- the repo isn't in the token's or the app installation's repository access
- a classic or OAuth token lacks the scope named (OAuth scope: ...)
Run ghostbranch doctor: it lists every permission the token lacks, for each command. A person grants error.details.permission on error.details.repo (in the fine-grained token's settings, or the Ghostbranch app installation's repository access), then runs the command in error.details.command again. Never ask for wider access than doctor names.
This needs a person.
LOCKEDexit 1Another ghostbranch operation on this pair is running on this machine (error.details.held_by names it).
Wait a few seconds and run the command again (error.retryable is true; next repeats it, or waits on error.details.job). The lock frees itself when that process ends, even if it crashed; there is nothing to delete.
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.
PLAN_STALEexit 6The repos changed since the plan was made.
Make a new plan with ghostbranch promote <feature> --plan and apply that.
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).
- 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)
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.
NO_OPEN_PRexit 2feature pull and feature archive need a feature's open public PR, and this feature (error.details.feature) has none (error.details.reason; error.details.public_pr once it was published).
- not_published: it was never published
- merged: its PR merged
- closed: its PR was closed without merging
- branch_gone: its public branch is gone
Follow next: for not_published, promote <feature> --plan publishes it; for merged, sync closes the feature; for closed or branch_gone, a person decides whether to promote again (a new PR) or close the feature.
FEATURE_ARCHIVEDexit 2This feature was handed off to the public repo (archived): its shadow branch is archived, so promote, gate, feature pull and waivers add no longer work on it, and feature start --from and feature restack --onto can't build on it (error.details.feature, error.details.public_pr, error.details.branch; error.details.pr_state says whether the PR is still open, merged or closed).
- apply archived it after publishing (Archive is apply's default)
- ghostbranch feature archive archived it
When feature start --from or feature restack --onto refused it (error.field from or onto), start from main or restack onto main instead, after ghostbranch sync if pr_state is merged. Otherwise, fix or change its PR by pushing to the PR's branch: git fetch origin <error.details.branch>, commit on origin/<error.details.branch> (never on the old shadow branch: the guard refuses its private commits), then git push origin HEAD:<error.details.branch>. When the PR merges, ghostbranch sync closes the feature; once it merged (pr_state merged) run sync, and once it was closed without merging (pr_state closed) a person closes the feature at the console. next gives these steps.
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.
APPROVAL_REQUIREDexit 5This needs a person: a code typed at a console. For promote and apply, an agent grant also works.
- no_console: no terminal a person can type into
- not_confirmed, timeout, too_many_attempts: the person did not confirm
- grant_needs_yes: a grant exists, but --yes was not passed
Stop and give the human the command in next. It needs a person at a console; for promote and apply, an agent grant made with ghostbranch agents allow also works (feature close and remove ignore grants). Never fake a terminal, answer the code, edit grants.json or run ghostbranch agents allow yourself.
This needs a person.
LOGIN_UNAVAILABLEexit 1Ghostbranch can't log in here, or can't read the stored login (error.details.reason says which).
- no_console: no terminal a person can use for the device flow; ghostbranch login --with-token reads a pasted token from stdin instead
- no_keychain: this machine has no usable OS keychain (for example Linux without a Secret Service), and Ghostbranch never stores a token in a plain file
- no_client_id: this build of Ghostbranch has no GitHub App client ID
- denied, expired_code: the person denied the request on GitHub, or the code expired before they approved it
- corrupt: the stored login can't be read
- keychain_error: the OS keychain exists but refused (for example, it is locked)
- refused: GitHub refused the login, for example because the app has device flow turned off
- refresh_failed: GitHub refused to refresh the stored login for a reason other than its expiry
Read error.details.reason. For no_console or no_keychain, set GHOSTBRANCH_TOKEN to a token instead; agents and CI always do. For no_client_id, use ghostbranch login --with-token or GHOSTBRANCH_TOKEN. For denied or expired_code, run ghostbranch login again and approve the code on GitHub. For corrupt, a person runs ghostbranch logout, then ghostbranch login. For keychain_error, unlock the keychain or set GHOSTBRANCH_TOKEN. For refused or refresh_failed, run ghostbranch login again.
This needs a person.