gitbutlerapp/gitbutler · error
`but merge` does not yet support triangular remotes
Error message
`but merge` does not yet support triangular remotes (fetch `{fetch_remote_name}`, push `{push_remote_name}`). Merge via a pull request instead, or configure a single remote for the target branch. What it means
Triangular workflows (fetch from one remote, push to another) are unsupported for `but merge`: the post-land reconcile reads the fetch remote's tracking ref, so pushing to a different remote would not advance it and reconciliation would silently no-op. `branch_land` refuses up front before mutating anything.
Solutions
- Configure a single remote for the target branch (same remote for fetch and push), then re-run `but merge`
- Land via a pull request instead (push the branch and open a PR on the hosting platform)
- Temporarily align remotes (`git remote set-url --push origin <fetch-url>`) if a direct landing is required
Example fix
// before (triangular)
{fetch: "upstream", push: "origin"}
// after (single remote)
{fetch: "origin", push: "origin"}
// or land via PR instead of `but merge` Defensive patterns
Strategy: validation
Validate before calling
let fetch_remote = &base_branch.remote_name;
let push_remote = &base_branch.push_remote_name;
if !push_remote.is_empty() && push_remote != fetch_remote {
println!("triangular setup unsupported by `but merge`; use a PR");
return Ok(());
} Try / catch
match land_result {
Err(e) if e.to_string().contains("triangular remotes") => {
open_pull_request_instead();
}
other => other?,
} Prevention
- Configure fetch and push to the same remote for the target branch when using GitButler landing
- For fork workflows, land changes via pull requests rather than `but merge`
- Audit remote configuration (`git remote -v`) before setting up the workspace target
When it happens
Trigger: Calling `but merge` when the configured target branch has `push_remote_name != fetch_remote_name` (e.g. fetch from `upstream`, push to `origin` fork setup).
Common situations: Open-source fork workflows (push to fork, pull from upstream); repos migrated to split push/pull remotes; CI configurations setting distinct push/pull URLs.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Ad-hoc (single-branch) branch moves are not supported…
- bare repositories are not supported
- cannot currently handle more than 1 parent
- Cannot merge : it shares no history with /
- Cannot merge : merging into / resulted in conflicts. Rebase…
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/4a5c91bb06ca6520.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-api/src/land/mod.rs:213
let target_branch_name = base_branch.short_name.clone();
if target_branch_name.is_empty() {
bail!("Configured target branch has no branch name");
}
let fetch_remote_name = base_branch.remote_name.clone();
let push_remote_name = if base_branch.push_remote_name.is_empty() {
fetch_remote_name.clone()
} else {
base_branch.push_remote_name.clone()
};
if push_remote_name.is_empty() {
bail!("Configured target branch has no push remote");
}
// Triangular remotes (fetch remote != push remote) are out of scope for now: the post-land
// reconcile reads the fetch remote's tracking ref, so a push to a different remote would not
// advance it and the reconcile would silently no-op. Refuse before mutating anything.
if push_remote_name != fetch_remote_name {
bail!(
"`but merge` does not yet support triangular remotes (fetch `{fetch_remote_name}`, \
push `{push_remote_name}`). Merge via a pull request instead, or configure a single \
remote for the target branch."
);
}
let target_display = format!("{push_remote_name}/{target_branch_name}");
let push_remote_url = if base_branch.push_remote_url.is_empty() {
&base_branch.remote_url
} else {
&base_branch.push_remote_url
};
let update_target_locally = {
let repo = ctx.repo.get()?;
remote_points_at_current_repo(&repo, push_remote_url)?
};
// Safety guards a non-CLI caller must not be able to bypass: never publish lower stack segmentsView on GitHub (pinned to 58e5313667)