gitbutlerapp/gitbutler · error
GitHub refused the merge
Error message
GitHub refused the merge ({status}): {reason} What it means
The pull request merge REST call completed at the transport level but GitHub returned an error status; the library extracts the 'message' field from the JSON error body (falling back to the raw body) and bails with the status code and reason. GitHub refuses merges for many precondition failures that are not network errors.
Solutions
- Parse the status/reason in the message: 405 usually means the merge method is disallowed or conflicts exist, 409 means no mergeable state, 422 often means already merged
- Wait for required status checks/reviews to pass before merging
- Use a merge method enabled in the repo settings, or enable it in repo settings
- If a merge queue is enabled, add the PR to the queue instead of calling the direct merge endpoint
Defensive patterns
Strategy: try-catch
Validate before calling
// precheck mergeability and required checks before merging
let pr = client.get_pull_request(owner, repo, pr_number).await?;
if !pr.mergeable || pr.draft { return Err(anyhow!("PR not mergeable yet")); } Try / catch
// branch on the status embedded in the message
match client.merge_pull_request(¶ms).await {
Err(e) if e.to_string().contains("405") => eprintln!("conflict or merge method disallowed"),
Err(e) if e.to_string().contains("409") => eprintln!("not mergeable yet; wait for checks"),
Err(e) => return Err(e),
Ok(()) => {},
} Prevention
- Wait for required status checks and reviews before merging
- Only request merge methods enabled in repo settings
- Check mergeable state and skip the call when the PR is already merged
- Use the merge queue API when branch protection requires it
When it happens
Trigger: Calling the merge PR API when: the merge method is not allowed on the repo, the branch has conflicting/unmergeable state, required status checks or reviews are missing, branch protection blocks the merge, or the PR was already merged/closed.
Common situations: CI still running so required checks are pending; merge queue enabled so direct merge is disallowed; 'Allow merge commit' disabled in repo settings while requesting merge method 'merge'; another bot merged the PR first (422 already merged).
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- Could not update all PR targets before native stack…
- Failed to get PR base repo
- Failed to merge pull request
- Failed to update pull request
- GitHub GraphQL convertPullRequestToDraft returned an empty…
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/f30d2882add265cf.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-github/src/client.rs:1492
merge_method,
};
let response = self.client.put(&url).json(&body).send().await?;
let status = response.status();
if !status.is_success() {
// The body carries GitHub's reason. Only its `message` reads well in
// a toast; the raw envelope drags a documentation URL along.
let body = response.text().await.unwrap_or_default();
let reason = serde_json::from_str::<serde_json::Value>(&body)
.ok()
.and_then(|value| {
value
.get("message")
.and_then(|m| m.as_str().map(String::from))
})
.unwrap_or(body);
bail!("GitHub refused the merge ({status}): {reason}");
}
Ok(())
}
/// Set the draftiness of a PR.
pub async fn set_pull_request_draft_state(
&self,
params: &SetPullRequestDraftStateParams<'_>,
) -> Result<()> {
if params.pr_number <= 0 {
bail!("PR number must be greater than 0");
}
let pull_request_id = self
.get_pull_request_node_id(params.owner, params.repo, params.pr_number)
.await?;
View on GitHub (pinned to 58e5313667)