gitbutlerapp/gitbutler · error · anyhow::Error

Failed to disable merge request auto-merge: {status} - {erro

Error message

Failed to disable merge request auto-merge: {status} - {error_text}

What it means

Thrown by GitLabClient::set_merge_request_auto_merge when params.enabled == false and GitLab rejects POST /projects/:id/merge_requests/:iid/cancel_merge_when_pipeline_succeeds. The dominant real-world failure is 406 Not Acceptable, which GitLab returns when auto-merge was never enabled — you cannot cancel what is not pending. Status and body are included in the message.

Source

Thrown at crates/but-gitlab/src/client.rs:516

                let status = response.status();
                let error_text = response.text().await.unwrap_or_default();
                bail!("Failed to enable merge request auto-merge: {status} - {error_text}");
            }

            return Ok(());
        }

        let url = format!(
            "{}/projects/{}/merge_requests/{}/cancel_merge_when_pipeline_succeeds",
            self.base_url, project_id, params.mr_iid
        );

        let response = self.client.post(&url).send().await?;

        if !response.status().is_success() {
            let status = response.status();
            let error_text = response.text().await.unwrap_or_default();
            bail!("Failed to disable merge request auto-merge: {status} - {error_text}");
        }

        Ok(())
    }

    pub async fn fetch_project(&self, project_id: GitLabProjectId) -> Result<GitLabProject> {
        self.fetch_project_by_path(project_id.to_string()).await
    }

    pub async fn fetch_project_by_numeric_id(&self, project_id: i64) -> Result<GitLabProject> {
        self.fetch_project_by_path(project_id.to_string()).await
    }

    async fn fetch_project_by_path(&self, project_id: String) -> Result<GitLabProject> {
        #[derive(Deserialize)]
        struct GitLabApiProject {
            id: i64,
            path_with_namespace: String,

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Treat 406 on cancel as success (the end state — auto-merge off — already holds)
  2. Refresh MR state before showing the cancel affordance, and hide it when merge_status is not pending auto-merge
  3. For 404, drop the stale MR from local state

Example fix

// before
client.set_merge_request_auto_merge(&params).await?;

// after: idempotent cancel
if let Err(err) = client.set_merge_request_auto_merge(&params).await {
    let msg = err.to_string();
    if !(msg.contains("406") || msg.contains("404")) {
        return Err(err); // 406 = never enabled, 404 = MR gone; both mean 'nothing to cancel'
    }
}
Defensive patterns

Strategy: fallback

Validate before calling

// Only offer cancel when MR state actually shows pending auto-merge (from a fresh get_merge_request)
ensure!(mr.merge_status_is_auto_merge_pending(), "auto-merge not enabled for MR {}"); // conceptually
client.set_merge_request_auto_merge(&params).await?;

Try / catch

if let Err(err) = client.set_merge_request_auto_merge(&params).await {
    let m = err.to_string();
    if m.contains("406") || m.contains("404") { /* nothing to cancel — treat as success */ }
    else { return Err(err); }
}

Prevention

When it happens

Trigger: Cancelling auto-merge on an MR where it was never enabled (406); auto-merge already fired because the pipeline finished and the MR merged (406/404); MR deleted while the cancel request was in flight (404); no permission (403).

Common situations: UI shows a cancel button based on stale state after the pipeline finished and auto-merge already executed; user cancelled in the web UI and the desktop client retries; double-click on cancel.

Related errors


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