Error and warning codes
Every error and warning code ghostbranch reports: what it means, why it happens and what to do.
Every code in a result’s error.code or warnings: 55 errors and 33 warnings. Open one to see its usual causes and what to do. ghostbranch explain <CODE> prints the same, and a link to a code here, such as LEAK_FOUND, opens it.
Errors
Section titled “Errors”An error stops the command. Its exit code says what kind of failure it is (see Exit codes).
Showing 55 of 55 codes
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.
AUTHOR_UNMAPPEDexit 1promote.author_email is map, and some commit authors are not in promote.author_map. Commits feature pull brought in from the public PR keep their authors and are never unmapped.
A person adds the emails in error.details.emails to promote.author_map in .ghostbranch.yml on the overlay branch, or switches author_email to noreply.
This needs a person.
AUTH_MISSINGexit 1No GitHub token was found.
- GHOSTBRANCH_TOKEN, GH_TOKEN and GITHUB_TOKEN are unset, there is no stored login (ghostbranch login), and `gh auth token` gave nothing
A person runs `ghostbranch login`, or sets GHOSTBRANCH_TOKEN. Agents and CI use a token of their own in GHOSTBRANCH_TOKEN; the permissions each command needs are in the token table of `ghostbranch guide`. `gh auth login` also works.
This needs a person.
BAD_FLAGexit 2A flag, argument or option value is invalid.
Fix the value named in error.field.
BRANCH_EXISTSexit 4The name is taken: by a shadow branch, by a public branch Ghostbranch didn't publish for this feature, or by the branch of this feature's closed PR.
With error.details.plan_id the branch holds an earlier, interrupted publish of this feature: resume it with ghostbranch apply <plan_id> rather than promoting under another name. Otherwise use another feature name, or promote with --branch <other-name>. A feature whose own PR is open is never refused with this: promote updates that PR.
CHECKS_FAILEDexit 3CI failed on the gate candidate.
Fix the failing checks listed in details.failed, push to the feature branch, and gate again.
CI_TIMEOUTexit 3CI did not finish in time.
Gate again, or raise gate.ci_timeout in .ghostbranch.yml.
CONFIG_INVALIDexit 1.ghostbranch.yml on the overlay branch is missing or invalid, or the overlay branch holds non-overlay paths.
A person fixes .ghostbranch.yml on the shadow repo's ghostbranch branch.
This needs a person.
CURATE_CONFLICTexit 4Replaying the curated groups in their order hit a conflict.
Change the group order in the --curate file so commits that touch the same lines keep their original order, or reorder the commits on your feature branch yourself. Ghostbranch never resolves conflicts.
CURATE_INVALIDexit 2The --curate file is malformed, or its groups don't name every feature commit exactly once.
- not JSON of the form {"commits": [{"from": [<sha>...], "message": "..."}]}
- a group with no commits or an empty message
- a SHA that is missing, repeated, ambiguous, or not one of the feature's commits
Fix the file using error.details (missing, repeated, unknown, ambiguous). The feature's commits are listed in the gate result's commits.
CURATE_TREE_MISMATCHexit 4The curated replay ends on a different tree from the one the gate's CI tested.
Keep commits that touch the same lines in their original order, or reorder on your feature branch, gate again, and use preserve.
DOCTOR_FAILEDexit 1ghostbranch doctor found failing checks; error.details.failed lists their IDs.
- one of tools, token, pair, guard, state or ci failed; data.checks[].message says why
Run the fix each failing check names (data.checks[].fix, also listed in next), then run ghostbranch doctor again. Warnings don't fail doctor.
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.
FEATURE_MOVEDexit 4A feature's branch or refs moved while it was being closed, so the close stopped; nothing more was deleted.
Run ghostbranch feature show <name> to see the new state, then close it again if that's still wanted.
FEATURE_NOT_FOUNDexit 2No such feature in the shadow repo.
Check the name, or start it with ghostbranch feature start.
FOLD_NEEDS_REBASEexit 4feature fold only fast-forwards, and the parent has moved on since the feature was branched from it, so nothing was folded.
- commits were pushed to the parent after the feature was started from it or last restacked onto it
- the parent was rewritten (rebased, amended or force-pushed)
Bring the feature up to its parent's tip, then fold again. ghostbranch feature restack <parent> does it, and stops with git steps on a conflict. Or rebase it yourself (error.details.steps): git fetch shadow && git switch <feature> && git rebase --onto shadow/<parent> <forked_at> <feature>, resolve any conflicts, then git push --force-with-lease shadow <feature>. error.details.parent and error.details.forked_at name them. Ghostbranch never resolves conflicts.
FORGE_ERRORexit 1A GitHub API or git transport call failed.
- network trouble
- GitHub outage or rate limit
- a token without the needed permission
Retry when retryable is true; otherwise read the message for the failing call.
GUARD_UNSUPPORTEDexit 1The pre-push guard cannot be installed here.
- git is older than 2.31
- the hooks directory is inside the working tree and the guard's files there (pre-push and pre-push.ghostbranch-*) are not all git-ignored, so they could be committed
- the checkout's hooks are managed by Husky
- a leftover pre-push.ghostbranch-chained file without a pre-push hook, or both files already exist
Upgrade git, ignore pre-push and pre-push.ghostbranch-* in the hooks directory, or point core.hooksPath at a directory that is ignored or outside the working tree.
HAS_CHILDRENexit 4The feature has children stacked on it (error.details.children), so it was not abandoned or closed: they would be left on a deleted parent.
Deal with each child in error.details.children first: fold it into this feature (ghostbranch feature fold <child>), abandon it (for abandon: ghostbranch feature abandon <child>) or close it (for close: ghostbranch feature close <child>), or move it onto main (ghostbranch feature restack <child> --onto main). An abandoned child blocks close, not abandon. Then run the command again.
INTERNALexit 1An unexpected local failure.
Read the message; report it if it repeats.
INTERRUPTEDexit 1The command was interrupted (Ctrl-C) before it finished.
With error.details.plan_id the plan is saved and the publish may have started: resume it with ghostbranch apply <plan_id> (never promote again; a person runs it unless an agent grant exists). With error.details.job the gate's CI is still running in the shadow repo: run ghostbranch wait <job>. With neither, nothing is pending: run the command again.
JOB_NOT_FOUNDexit 2The feature has no gate job to wait on: it was never gated, or its last gate stopped before CI (a leak or a conflict).
Run ghostbranch gate <feature>, with --detach to get a job id.
JOB_STALEexit 4A newer gate replaced this job: the feature, public main or the overlay changed, and the feature was gated again.
Wait on the current job (error.details.current_job), or, if there is none, gate the feature again.
LEAK_FOUNDexit 3The leak scan found something in what would be published.
Remove the content in details.findings from the feature and gate again. A finding whose commits are listed may be only in older commits: publish with --mode squash or --mode curated so they never ship, or rewrite those commits on the feature branch, force-push it to the shadow repo, and gate again. If a person judges a finding in details.waivable a false positive, they run the waivers add command in next. Agents never waive findings or edit waivers.json.
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.
LOGIN_EXPIREDexit 1The stored GitHub login has expired and can't be refreshed.
- its refresh token is past its six months
- the login was revoked on GitHub, or the Ghostbranch app was uninstalled or its authorization removed
A person runs ghostbranch login again. An agent stops and hands this to the person; an agent with a token of its own sets GHOSTBRANCH_TOKEN, which never uses the stored login.
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.
MARKER_INVALIDexit 3A file has BEGIN-INTERNAL/END-INTERNAL markers that cannot be stripped safely.
- unmatched_begin: a begin marker with no end
- unmatched_end: an end marker with no begin
- nested: a begin marker inside an open section
- malformed: a line that looks like a marker but is not exactly one (more text, a doubled prefix, or a multi-line comment)
- unsupported_encoding: markers in a UTF-16 or UTF-32 file, or in a file read as binary
Fix the marker lines at details.problems on the feature branch, push it to the shadow repo, and gate again. When details.commit names an older commit and the final tree no longer has the problem, publish with --mode squash or --mode curated so it never ships, or rewrite that commit on the feature branch and force-push it to the shadow repo. Never delete only the marker lines: the internal lines would then be published.
MISSING_INPUTexit 2Required input is missing.
Pass the input named in error.field, for example --title or -m for promote.
NAME_INVALIDexit 2A feature or branch name is invalid or reserved.
Choose another name; main, HEAD and names starting with ghostbranch are reserved.
NOTHING_TO_PUBLISHexit 1Nothing would be published (error.details.reason).
- no_commits: the feature has no commits of its own yet
- all_stripped: every commit is empty, or the commits cancel out, once overlay paths and internal sections are stripped (error.details.dropped_empty)
- an update of an open PR that already holds every change (no reason)
For no_commits, commit on the feature branch, push it to the shadow repo, then gate. Otherwise there is nothing to do: the feature has no public changes.
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.
NOT_A_STACKexit 2The features given to promote aren't a stack that can be published together; error.details.reason says why and error.details.feature names where.
- duplicate: a feature is listed twice
- order: the features aren't listed bottom first
- gap: a listed feature's parent is neither the feature listed just below it nor already public (for example promote a c, leaving out b)
- parent_not_public: the bottom feature's parent is neither main nor already published
- parent_merged: the bottom's published parent merged; run sync
- parent_moved: the bottom's published parent changed since it was published
- stale_gate: a listed feature has no fresh, green gate; a gate goes stale when its own branch or any branch below it moves. With error.details.update_mode, the bottom's gate built its open PR's update in another history mode (gate builds in promote.default_mode), so gating again won't help: promote that feature on its own with --mode <update_mode>, which gates it in that mode, then the branches above it. error.details.updating is true when the stack's PRs are open: the new plan updates them all.
- open_above: two or more lines of open PRs stack on one published feature, and one plan updates one line: close or merge every other line's PRs on that feature on GitHub, then promote it again
- parent_stale: a branch above wasn't restacked after the branch below moved; restack, gate up, promote
Run ghostbranch status to see the stack. List the features bottom first, starting from one whose parent is main or already published, with none left out. Gate each one that needs it (ghostbranch gate <feature>, bottom up), then run promote again.
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.
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.
OVERLAY_COLLISIONexit 4Public main already has paths Ghostbranch reserves for the overlay.
A person moves those paths out of public main.
This needs a person.
PAIR_AMBIGUOUSexit 2Ghostbranch cannot tell which repo pair to use.
Pass --repo owner/name, or --repo . inside a checkout of the public repo.
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.
PARENT_REWRITTENexit 4The feature's parent no longer contains the commit the feature was branched from (forked_at): the parent was rebased, amended or force-pushed.
Run ghostbranch feature restack <parent> to replay the feature's own commits onto the parent's new tip (it stops with git steps on a conflict), or ghostbranch feature restack <feature> --onto main to move it off the parent. Then run the command again. error.details.parent and error.details.forked_at name them.
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.
PLAN_STALEexit 6The repos changed since the plan was made.
Make a new plan with ghostbranch promote <feature> --plan and apply that.
PUBLIC_BRANCH_CHANGEDexit 4The open PR's public branch holds commits Ghostbranch didn't publish (error.details.foreign), so the PR was not updated: the update would drop them. error.details.reason, when set, names a special case.
- someone pushed a commit to the PR's branch
- a review suggestion was committed on GitHub
- Update branch on GitHub merged main into the PR's branch
- the branch was force-pushed outside Ghostbranch
- unrecorded_update: the branch is at an update Ghostbranch pushed whose record was never written (error.details.plan); nothing is foreign
- adopted_missing: a preserve update would drop commits feature pull brought in from GitHub that the feature no longer has (error.details.foreign)
Bring them into the feature with ghostbranch feature pull <feature>, then gate and promote again. They are already public, so the gate doesn't scan them again; it scans your commits as always. If feature pull can't replay them cleanly, it stops with the git steps to merge them in yourself. For unrecorded_update, run ghostbranch apply <error.details.plan> on the machine that made the plan: it finds the push landed and writes the record (never feature pull, which would copy Ghostbranch's own commits into the feature). For adopted_missing, cherry-pick the listed commits back onto the feature, push it to the shadow repo, then gate and promote again. Ghostbranch never resolves conflicts, and never overwrites commits made on GitHub. error.details.foreign lists each commit's sha, author and subject.
PUSH_BLOCKEDexit 1The pre-push guard refused to push feature work to the public repo.
- pushing a feature branch, the overlay branch or a gate branch to the public remote
- pushing commits that contain a shadow feature
Publish features only with ghostbranch promote. Never bypass the hook with --no-verify.
PUSH_REFUSEDexit 1The remote repo refused the push.
- the token lacks a permission the push needs, for example Workflows write (fine-grained) or the workflow scope (gh/OAuth) to change .github/workflows files
- a repository ruleset or branch protection (GH013)
- a server-side hook
Read error.details.reason. Fix the cause (usually a token permission a person grants) and run the command again; retrying unchanged will be refused again.
This needs a person.
REBASE_CONFLICTexit 4The feature does not rebase cleanly onto public main.
Rebase the feature branch onto public main yourself, resolve the files in details.files (the commit in details.commit stopped), push, and gate again.
REF_MOVEDexit 4A ref changed on the remote while the command ran.
Run the command again.
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_CREATE_DENIEDexit 1The token may not create the shadow repo.
- a fine-grained token can't name a repo that doesn't exist yet, so creating one needs Administration write on all of the owner's repositories
- the token lacks Administration write in the owner account or org
A person creates the empty private repo in error.details.shadow (no README, license or .gitignore), on GitHub's new-repository page or with `gh repo create <shadow> --private`, and adds it to the token's repository access (see the token table in ghostbranch guide). Then run the init --adopt command in next. Never ask for a token with wider access.
This needs a person.
SHADOW_NOT_EMPTYexit 4The repo given to init --adopt has unrelated content.
A person picks an empty repo or a pure mirror of the public repo.
This needs a person.
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.
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.
TOKEN_CANNOT_PUBLISHexit 1The token in use cannot push to the public repo.
- the token is scoped to the shadow repo only (the strict-isolation setup)
- the token lacks Contents: write on the public repo
A person publishes from their own terminal with a token that can write to the public repo (`ghostbranch apply <plan>`).
This needs a person.
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.
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.
WAIVER_UNKNOWNexit 2waivers add named an id that is not a waivable finding in any commit the feature would publish now (it scans every commit, as a preserve promote does; under a squash or curated default whose older commit has a broken marker, only the final tree), or waivers revoke named an id with no stored waiver for the repo.
- the content changed, so the finding has a new id
- the finding was only in a commit that is no longer on the feature branch (waivers add scans every commit on it, whatever the default mode, except that under a squash or curated default whose older commit has a broken marker it scans only the final tree)
- the finding is in the commit message, branch or PR text, which is never waivable
- a typo
- for waivers revoke: the waiver was already revoked, or is for another repo
For waivers add, gate again and use the ids in error.details.waivable. For waivers revoke, run ghostbranch waivers list --repo <public> and revoke the ids it shows; error.details.ids lists the ids with no waiver.
No codes match.
Warnings
Section titled “Warnings”A warning comes with a result: the command still ran.
Showing 33 of 33 codes
ACTIONS_MINUTES_BILLEDwarningThe shadow repo is private, so its Actions minutes are billed to its owner.
Nothing is required. Scheduled sync bills at least one minute a run (every 6 hours is about 120 minutes a month); turn it off with sync.schedule: off and ghostbranch upgrade.
ACTOR_UNKNOWNwarningGHOSTBRANCH_ACTOR is unset and the GitHub login wasn't read; the ledger records human:unknown.
Set GHOSTBRANCH_ACTOR, for example agent:claude-code.
AGENT_ON_PERSON_LOGINwarningThis run looks like an agent or a script, and it is using a person's stored login, which can publish.
Give the agent its own shadow-only token in GHOSTBRANCH_TOKEN (see ghostbranch guide § Tokens). The person's login still needs the console code to publish.
ARCHIVE_INCOMPLETEwarningThe publish succeeded, but handing a feature off (archiving its shadow branch) stopped partway (details.feature; details.step is push, gate_pr or record; details.code is the step's error).
Nothing is undone: the PR is public. Run ghostbranch feature archive <details.feature>: every step is safe to run again, and it archives the branch's head as it is then, so nothing is lost. Until it finishes, don't push to the PR's branch: the guard may still refuse it.
AUDIT_INCOMPLETEwarninginit or upgrade finished, but the CI audit after it could not run.
Run ghostbranch audit.
BINARY_FILEwarningNew or changed binary files would be published.
Check that the files in details.paths are meant to go public; they were scanned only through their printable text.
CHECKS_LIMITEDwarningThe token cannot read check runs, so only GitHub Actions workflows and commit statuses were considered.
Use a token that can read checks if you rely on CI apps other than Actions.
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.
CONFIG_KEY_IGNOREDwarning.ghostbranch.yml has a key this version does not use.
Nothing is required; remove the key to silence the warning.
DRIFT_UNKNOWNwarningGhostbranch couldn't check whether a feature still merges cleanly onto public main.
Run ghostbranch gate <feature>; the gate is the authority on rebasing.
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.
GATE_NOT_RECORDEDwarningThe gate result could not be saved to the feature metadata; the result itself stands.
With details.job, run ghostbranch wait <job>: it records the outcome the job's CI gave. Without it (the gate stopped on a leak), fix the leak and gate again. promote needs a recorded green gate.
LARGE_FILEwarningNew or changed files are larger than leak_scan.large_file.
Check that the files in details.paths are meant to go public.
LEDGER_NOT_WRITTENwarningThe command did what its result says, but its entry in the ledger (ghostbranch log) couldn't be written.
Nothing is undone and nothing needs running again. Keep details.refs when it is there: after a close it lists every deleted ref with its SHA, which the ledger would have kept for recovering a branch. If it repeats, check that the workspace's ledger file is writable and the disk has room.
MIRROR_NOT_UPDATEDwarningThe shadow repo has the change, but this machine's mirror of it (a ref, or a plan file) couldn't be updated.
- the mirror directory is not writable, or the disk is full
- a stale lock file in the mirror
Nothing is required: the shadow repo is the truth, and the next command's fetch corrects the mirror. If it repeats, check the path in details and the disk.
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.
PLAN_FILE_UNREADABLEwarningA plan file on this machine couldn't be read, so it was skipped.
Look at details.path: fix its permissions or delete it. If it belonged to a feature that was just closed, that feature's plan ref may remain in the shadow repo; it is harmless.
PR_STATE_UNKNOWNwarningGhostbranch couldn't read a published feature's public PR state.
Run the command again; a feature whose PR state is unknown is never closed automatically.
PR_TEXT_NOT_SETwarningApply recorded an update whose push had already landed, but didn't set the PR title or body its plan types: setting them needs approval, which wasn't given.
The PR keeps its texts. To change them, make a new plan with ghostbranch promote <feature> --title/--body --plan (details.feature) and have a person approve it.
PUBLIC_BRANCH_CHANGEDwarningA published feature's public branch holds commits Ghostbranch didn't publish, so sync didn't adopt them; or (details.reason unrecorded_update) it is at an update Ghostbranch pushed but never recorded.
Look at the PR on GitHub: someone pushed to its branch. Bring the commits in with ghostbranch feature pull <feature> (details.feature), then gate and promote; until then promote refuses to update the PR. Ghostbranch never adopts a change it didn't scan. For unrecorded_update, run ghostbranch apply <details.plan> on the machine that made the plan: it writes the record.
PUBLIC_NOT_PUBLICwarningThe public repo is private.
Ghostbranch is meant for public repos; check you named the right repo.
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.
RECORD_NEWERwarningA record was written by a newer Ghostbranch; this binary shows what it can read of it and won't change it.
Upgrade ghostbranch on this machine (the other machine is probably newer). Reads still work; commands that would change the record stop with UPGRADE_NEEDED.
RECORD_UNREADABLEwarningA feature's record couldn't be read (corrupt, or from a newer Ghostbranch); a person at the console closed the feature anyway.
Nothing more to do: the feature's refs were deleted and logged in the ledger with their SHAs, so a branch can still be recovered.
REPO_GUARDwarningA workflow job or step is guarded to the public repo's name, so it skips in the shadow repo (ci.rewrite_repo_guards is warn).
Set ci.rewrite_repo_guards: rewrite (the default) so the gate's CI runs it, or widen the guard in the public workflow (which writes the shadow repo's name into the public repo). Keep ci.rewrite_repo_guards: warn if a guarded job deploys or publishes: rewritten, it runs in the shadow repo on unreleased code, and its secrets must never be added there.
REPO_GUARD_UNREWRITABLEwarningA workflow guard mentions the public repo in a form the gate can't rewrite safely, so that job skips in the shadow repo and CI does not test it.
- startsWith, contains, fromJSON or format around github.repository
- a YAML form the rewrite can't re-emit safely, such as an anchor or a multi-line plain value
A person widens the guard in the public workflow to also allow the shadow repo, for example github.repository == 'org/project' || github.repository == 'org/project-shadow'. That writes the shadow repo's name into the public repo. Never widen the guard of a job that deploys or publishes: it would run in the shadow repo on unreleased code, and its secrets must never be added there; repos whose guarded jobs deploy should use ci.rewrite_repo_guards: warn. Guards in run: scripts, reusable workflows called by repo name and composite actions are never detected.
SECRETS_MISSINGwarningWorkflows reference secrets the shadow repo doesn't have.
A person adds only the secrets the tests need. Never add deploy or publish credentials to the shadow repo: guarded jobs run there on unreleased code. Ghostbranch never reads or copies secret values.
This needs a person.
SECRETS_UNLISTEDwarningThe token can't list the shadow repo's secret names, so the audit could not tell which referenced secrets are missing.
Check that the names in data.secrets.referenced exist in the shadow repo, or give the token Secrets: read.
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_PUSH_FAILEDwarningSync couldn't push one of sync.branches to the shadow repo; it still pushed the others, cancelled their runs and closed merged features.
details.branch is the branch and details.code the push's error code. For REF_MOVED, run ghostbranch sync again. For PUSH_REFUSED or a permission error, a person fixes the token or the shadow repo's rules, then runs sync again.
UPDATE_AVAILABLEwarningA newer Ghostbranch release is out (details.latest; this is details.current). details.security is true when it fixes a security issue that affects this version (details.advisories).
Tell the person, quoting details.upgrade (the command that upgrades this install) and, for a security fix, details.advisories. Never run the upgrade yourself. GHOSTBRANCH_NO_UPDATE_CHECK=1 turns the check off.
WAIVERS_UNREADABLEwarningwaivers.json could not be read, so no waiver applied and every finding blocked.
A person fixes the file or clears it with ghostbranch waivers revoke --all. If the message says a newer Ghostbranch wrote it, upgrade ghostbranch on this machine instead: revoke --all keeps a newer file.
This needs a person.
WORKFLOW_CHANGEDwarningWorkflow files under .github/workflows are new or changed in what would be published.
A person checks the workflow changes in details.paths before approving the publish.
No codes match.