gitbutlerapp/gitbutler · error · anyhow::Error

Failed to merge pull request: {status} - {error_text}

Error message

Failed to merge pull request: {status} - {error_text}

What it means

Raised when POSTing to /repositories/{workspace}/{repo_slug}/pullrequests/{id}/merge returns non-2xx; the message includes the status and Bitbucket's response body, which names the unmet merge precondition. The request body is built just above (merge strategy/close-source flags), so invalid strategy combinations also surface here.

Source

Thrown at crates/but-bitbucket/src/client.rs:435

        Ok(())
    }

    pub async fn merge_pull_request(&self, params: &MergePullRequestParams<'_>) -> Result<()> {
        let body = MergePullRequestBody {
            merge_strategy: params.strategy.as_str(),
        };
        let url = format!(
            "{}/repositories/{}/{}/pullrequests/{}/merge",
            self.base_url,
            urlencoding::encode(params.workspace),
            urlencoding::encode(params.repo_slug),
            params.id,
        );
        let response = self.client.post(&url).json(&body).send().await?;
        if !response.status().is_success() {
            let status = response.status();
            let error_text = response.text().await.unwrap_or_default();
            bail!("Failed to merge pull request: {status} - {error_text}");
        }
        Ok(())
    }

    /// Decline (close) a pull request.
    pub async fn decline_pull_request(
        &self,
        workspace: &str,
        repo_slug: &str,
        id: i64,
    ) -> Result<()> {
        let url = format!(
            "{}/repositories/{}/{}/pullrequests/{}/decline",
            self.base_url,
            urlencoding::encode(workspace),
            urlencoding::encode(repo_slug),
            id,
        );

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Parse {error_text} — Bitbucket states the failed merge check (conflicts, approvals, builds)
  2. Re-fetch the PR and verify state is OPEN and all checks green before retrying
  3. Resolve conflicts / collect approvals, then retry the merge
  4. Treat 'already merged' 409s as success by refreshing PR state instead of surfacing an error
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check the merge gates you can observe
let pr = client.get_pull_request(ws, slug, id).await?;
anyhow::ensure!(pr.state == "OPEN", "PR not open");
if let Some(status) = client.latest_build_status(ws, slug, &pr.source_commit()).await? {
    anyhow::ensure!(status.is_success(), "build not green: {}", status.state);
}

Try / catch

match client.merge_pull_request(&params).await {
    Err(e) if e.to_string().contains("409") => {
        // someone else may have merged it — refresh and treat merged as done
        let pr = client.get_pull_request(params.workspace, params.repo_slug, params.id).await?;
        if pr.state == "MERGED" { Ok(()) } else { Err(e) }
    }
    r => r,
}

Prevention

When it happens

Trigger: Merging a PR with unresolved conflicts, failing or missing required builds, missing required approvals (branch restrictions), a PR already merged or declined (409), or a merge strategy the repo does not permit.

Common situations: Automation merging as soon as CI finishes while required reviewers are still pending; double-merge races between two clients; repo admins tightening branch restrictions after the PR was opened.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/70cd431c938a7995. Report an issue: GitHub.