gitbutlerapp/gitbutler · error
GitForcePushProtection
GitForcePushProtection
Error message
The force push was blocked because the remote branch contains commits that would be overwritten.
{e} What it means
Returned from push_with_askpass when the push was initiated with force_push_protection enabled and gix reported ForcePushProtection: the remote ref points at commits that the local push would overwrite (the remote branch contains commits not present locally, i.e. the update is not a strict fast-forward of what was expected). It carries Code::GitForcePushProtection so the UI/API can offer an explicit 'force anyway' action. GerritNoNewChanges and NonFastForward are handled as separate cases right below it.
Source
Thrown at crates/gitbutler-git/src/context.rs:290
})
.join()
.map_err(|panic| {
let reason = if let Some(message) = panic.downcast_ref::<String>() {
message.clone()
} else if let Some(message) = panic.downcast_ref::<&'static str>() {
(*message).to_owned()
} else {
"unknown panic payload".to_owned()
};
anyhow!("git push worker thread panicked: {reason}").context(
but_error::Context::new("git push failed unexpectedly").with_code(Code::Unknown),
)
})??;
match result {
Ok(stderr) => Ok(stderr),
Err(err) => match err {
crate::Error::ForcePushProtection(e) => Err(anyhow!(
"The force push was blocked because the remote branch contains commits that would be overwritten.\n\n{e}"
)
.context(Code::GitForcePushProtection)),
crate::Error::GerritNoNewChanges(_) => {
// Treat "no new changes" as success for Gerrit.
Ok(String::new())
}
crate::Error::NonFastForward(_) => Err(err).context(Code::GitNonFastForward),
_ => Err(map_needs_authorization(err)),
},
}
}
fn serialize_branch_to_remote<S>(
branch_to_remote: &[(String, gix::refs::FullName, String)],
serializer: S,
) -> std::result::Result<S::Ok, S::Error>
whereView on GitHub (pinned to caf1f223d3)
Solutions
- Fetch and integrate the remote commits first (rebase/merge your stack onto the updated origin branch), then push again
- If overwriting is intended, push with the force flag while consciously disabling force_push_protection for that call (the UI's 'force push' confirmation does exactly this)
- Verify what would be lost: `git log --oneline <local>..<remote-branch>` before forcing
- If CI noise causes this, coordinate branch ownership or enable push --force-with-lease semantics
Example fix
// before ctx.push(head, target_ref, /* with_force */ true, /* force_push_protection */ true, None, askpass, vec![])?; // after: integrate remote state first, then the protected push succeeds ctx.fetch(&remote_name, None)?; // ... rebase the stack onto the updated origin branch ... ctx.push(head, target_ref, true, /* protection now passes */ true, None, askpass, vec![])?;
Defensive patterns
Strategy: validation
Validate before calling
// pre-check divergence before a protected force push
ctx.fetch(&remote_name, None)?;
let remote_ref = format!("refs/remotes/{remote_name}/{branch}");
let remote_id = repo.find_reference(&remote_ref)?.peel_to_commit()?.id;
let base = repo.merge_base(remote_id, local_head)?;
if base != Some(remote_id) {
// remote has commits local does not contain: the protection WILL fire
anyhow::bail!("remote {remote_ref} moved ahead — rebase/merge first or force without protection");
} Try / catch
use but_error::Code;
match ctx.push(head, target, true, true, None, askpass, vec![]) {
Ok(stderr) => Ok(stderr),
Err(err) if err.downcast_ref::<Code>() == Some(&Code::GitForcePushProtection)
|| err.to_string().contains("force push was blocked") =>
{
// surface to the user: integrate remote changes, or retry with protection off
offer_force_push_confirmation()
}
Err(err) => Err(err),
} Prevention
- Fetch before every push so protection decisions use current remote state
- Rebase/amend locally and integrate others' commits before pushing rewritten history
- Reserve force_push_protection=false for explicit user-confirmed force pushes only
- Check `git log --oneline local..remote` to see exactly what a force would discard
When it happens
Trigger: Pushing a virtual branch/workspace over refs/remotes origin state that has moved ahead (someone else pushed); force_push_protection=true with stale local remote-tracking refs because no fetch ran recently; rebasing/rewriting a stack and pushing with force while protection is on; concurrent pushes from another machine or CI.
Common situations: Two developers or a developer plus CI pushing to the same branch; pushing after history rewrite (rebase, amend) with the safety check enabled; the app's remote-tracking ref is stale relative to the actual server state.
Related errors
- failed to push {} of {} branch{}
- failed to find branch {target_branch_refname}
- Failed to communicate with LM Studio server: ${error instanc
- Invalid response: ${JSON.stringify(result)}
- When using GitButler's API to summarize code, you must be lo
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/2e812ac93344efc1.
Report an issue: GitHub.