The push guard
The git hook that stops feature work from reaching your public repo by a plain git push.
Your checkout has both remotes, origin and shadow, so one wrong git push origin could publish unreleased work without any gate. The guard runs inside every git push, so it catches that push even when you don’t notice the remote’s name.
What it blocks
Section titled “What it blocks”When you push to the public repo, the guard checks what you’re pushing and refuses:
- a branch named after one of your features, whatever it holds, except an archived feature’s own pull request branch;
- any push that carries a feature commit that hasn’t been published, under any branch name;
- the overlay branch
ghostbranchand the gate branches; - a pushed ref that isn’t a commit.
A new name doesn’t get past it, because it looks at the commits:
In both blocks, the guard’s lines are from real runs of its check, and the last line is the one git prints when a hook stops a push.
Nothing was sent. Pushes to the shadow repo, and ordinary pushes to the public repo, such as main, go through untouched. To publish a feature, use ghostbranch promote.
The guard knows your features from Ghostbranch’s copy of the shadow repo on your machine. Run ghostbranch sync now and then, so it has seen features started on your other machine.
Archived features: the one exception
Section titled “Archived features: the one exception”After apply hands a feature off (Archive, its default), the feature’s own pull request branch takes plain pushes. That’s how you fix the pull request from then on: no gate, no leak scan, no plan and no code. Start from the public branch, not the old shadow branch:
git fetch origin add-searchgit switch --detach origin/add-search# commit your fix heregit push origin HEAD:add-searchThe exception is narrow. The guard still refuses:
- a push that carries the archive’s private commits, the shadow branch’s history that never went public. A plain
git switch add-searchlands on the old shadow branch, so a fix committed there is refused; - other features’ unpublished commits, and the overlay and gate branches, as before;
- the branch name, when the guard can’t read the feature’s record or find its archive. It fails closed.
Here a fix was committed on the old shadow branch and pushed to the pull request. The guard refuses it, because the branch carries the feature’s private commit:
The guard’s lines are from a real run of its check, as above. Start again from origin/add-search, and make the fix there.
Promote and approval explains Archive and Keep.
Install it
Section titled “Install it”ghostbranch init offers to install the guard when you set up a repo. To install it later, run this in your checkout:
It’s the person’s command, not an agent’s: an agent may suggest it, never run it. Add --plan to see what it would change first.
If the checkout already has a pre-push hook, it keeps running: the guard renames it pre-push.ghostbranch-chained and calls it after its own check. A hook that decides what to do from its own file name may behave differently.
ghostbranch guard uninstall removes the guard and puts back any hook it chained:
Check it
Section titled “Check it”ghostbranch guard status says whether the guard is installed in this checkout, and which public repo it covers through which remote:
ghostbranch doctor checks it too, among the rest of your setup.
guard check
Section titled “guard check”The hook runs ghostbranch guard check <remote> <url>, with the list git hands every pre-push hook on its input. You never need to run it yourself. When a push is fine it prints nothing, so a guarded push looks like any other.
If the guard can’t tell what a push contains, or can’t read your features, it refuses the push rather than guess, and says why.
Its limits
Section titled “Its limits”The guard stops mistakes. It isn’t a wall:
- It covers only the checkout it’s installed in. Install it in each clone, on each machine.
git push --no-verifyskips every pre-push hook, the guard included. Never use it to get past the guard.- It recognizes GitHub remotes by their URL. An SSH host alias, such as
git@github-work:…, isn’t recognized. - It can’t recognize commits that were rewritten after they left the shadow repo, such as a cherry-pick onto a new branch.
- It reads Ghostbranch’s state from the default place, unless
GHOSTBRANCH_HOMEis set where git runs the hook.
When it can’t be installed
Section titled “When it can’t be installed”guard install refuses with GUARD_UNSUPPORTED when:
- git is older than 2.31;
- the checkout’s hooks are managed by Husky;
- the hooks directory is inside the working tree, and the guard’s files there aren’t ignored by git, so they could be committed;
- a leftover
pre-push.ghostbranch-chainedfile is in the way.
Upgrade git, ignore the files, or point core.hooksPath at a directory outside the working tree, then install again.
Errors you may see
Section titled “Errors you may see”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.
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.