gitbutlerapp/gitbutler · error
Failed to land onto
Error message
Failed to land {branch} onto {target_display} What it means
This is the fallback message thrown by `branch_land` (crates/but-api/src/land/mod.rs:315) after the push/retry loop finishes without producing a `LandOutcome`. The loop normally exits via fast-forward/merge/already-integrated success or via a concrete push error, so this arm is an internal invariant violation: the landing machinery exhausted its attempts without recording a result or an error. It means GitButler could not determine any landing outcome for the branch onto the target ref.
Solutions
- Retry the `but land` command — the target may have stabilized since the race window.
- Fetch the target remote (`but pull` / `git fetch`) so the local tracking ref is current, then retry landing.
- Check the target branch for concurrent automation (CI merges, bots) and pause it while landing.
- If it reproduces consistently, report a bug — this path is documented as unreachable in practice (internal invariant violation).
Example fix
// before but land feature-x --to main // fails: Failed to land feature-x onto origin/main // after git fetch origin && but pull && but land feature-x --to main
Defensive patterns
Strategy: retry
Validate before calling
null
Type guard
null
Try / catch
// TypeScript (Tauri/SDK caller)
try {
await api.branchLand({ branch, target });
} catch (e) {
if (String(e).includes('Failed to land')) {
await api.fetchTarget(); // refresh tracking refs, then retry once
await api.branchLand({ branch, target });
}
} Prevention
- Pause CI merges and bots that push to the target branch while landing.
- Fetch the target remote immediately before landing to minimize the race window.
- Treat repeated occurrences as a bug report against the land loop, not a user error.
When it happens
Trigger: Calling `branch_land` (or `but land <branch>`) when the internal push loop exits without setting `landed` — theoretically only reachable if every `MAX_PUSH_ATTEMPTS` attempts hit a retryable concurrency race and the guard above failed to fire, i.e. an unreachable-in-practice defensive bail.
Common situations: Hitting a persistent race where the target branch is being moved concurrently (e.g. CI or a teammate pushing to the same base branch during every retry); running an older/patched build where the retry-limit guard behaves differently; internal bugs in the land loop.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Branch ` ` is pushed, but its remote ancestry does not…
- Failed to create blank commit in stack
- failed to push of branch
- GitForcePushProtection
- pre-push hook failed
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/64ac40f060718a6d.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-api/src/land/mod.rs:315
{
fetch_target_remote(ctx, &fetch_remote_name)?;
}
Err(err) if deliver::is_retryable_concurrency_error(&err) => {
return Err(err.context(format!(
"Target branch kept moving; fetched and retried {MAX_PUSH_ATTEMPTS} times"
)));
}
Err(err) if update_target_locally => {
return Err(err.context("Failed to update local target branch"));
}
Err(err) => return Err(err.context("Failed to push to target branch")),
}
}
let Some(landed) = landed else {
// The loop only exits without a result when every attempt hit a retryable race; that path
// already returned an error above, so this is unreachable in practice.
bail!("Failed to land {branch} onto {target_display}");
};
// On the real-remote path, re-fetch so the tracking ref reflects the landed commit, then verify
// it actually advanced before reconciling — otherwise the reconcile would be a silent no-op.
if let BranchLandKind::Updated { new_target_oid, .. } = &landed
&& !update_target_locally
{
fetch_target_remote(ctx, &fetch_remote_name)?;
// A concurrent push may have moved the tip *past* our commit, which still counts as the
// target having advanced to include what we landed — so test reachability, not equality.
let advanced = {
let repo = ctx.repo.get()?;
match peel_target_tip(&repo, &fetch_remote_name, &target_branch_name)? {
Some(tip) => target_ref_contains(&repo, *new_target_oid, tip)?,
None => false,
}
};View on GitHub (pinned to 58e5313667)