{"record":{"id":"eafc323cc2d1f9b3","repo":"gitbutlerapp/gitbutler","slug":"failed-to-set-merge-request-draft-state-status","errorCode":null,"errorMessage":"Failed to set merge request draft state: {status} - {error_text}","messagePattern":"Failed to set merge request draft state: (.+?) - (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-gitlab/src/client.rs","lineNumber":534,"sourceCode":"            title: &'a str,\n        }\n\n        let url = format!(\n            \"{}/projects/{}/merge_requests/{}\",\n            self.base_url, project_id, params.mr_iid\n        );\n\n        let response = self\n            .client\n            .put(&url)\n            .json(&UpdateMergeRequestBody { title: &next_title })\n            .send()\n            .await?;\n\n        if !response.status().is_success() {\n            let status = response.status();\n            let error_text = response.text().await.unwrap_or_default();\n            bail!(\"Failed to set merge request draft state: {status} - {error_text}\");\n        }\n\n        Ok(())\n    }\n\n    pub async fn set_merge_request_auto_merge(\n        &self,\n        params: &SetMergeRequestAutoMergeParams,\n    ) -> Result<()> {\n        let project_id = params.project_id.clone();\n        if params.enabled {\n            #[derive(Serialize)]\n            struct EnableAutoMergeBody {\n                auto_merge: bool,\n            }\n\n            let url = format!(\n                \"{}/projects/{}/merge_requests/{}/merge\",","sourceCodeStart":516,"sourceCodeEnd":552,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-gitlab/src/client.rs#L516-L552","documentation":"set_merge_request_draft_state toggles an MR between draft and ready via the GitLab API. Any non-2xx response triggers this bail, embedding both the HTTP status and GitLab's JSON error body, so the message usually contains GitLab's own reason (e.g. 403 insufficient permissions, 404 wrong MR iid).","triggerScenarios":"Calling set_merge_request_draft_state with an MR iid that does not exist in the project (404), a token lacking push/merge permissions (403/401), an invalid or expired token (401), or a malformed request body rejected by GitLab (400/422).","commonSituations":"MR was deleted or iid came from a different project; token scope changed after creation; user tries to mark ready an MR owned by another user without permissions; GitLab instance is a version that rejects certain title changes.","solutions":["Read the embedded error_text — for 404 verify the project id and MR iid are correct and the MR still exists.","For 401/403, check the token's scopes (api scope required) and the user's role in the project.","Confirm the MR exists and get its current draft state via the API before toggling to avoid redundant no-op calls that may still fail.","Handle 429/5xx with a retry with backoff since these are transient server-side conditions."],"exampleFix":"// before\nmatch client.set_merge_request_draft_state(project_id, iid, false).await {\n    Ok(()) => {}\n    Err(e) => panic!(\"{e}\"),\n}\n// after\nmatch client.set_merge_request_draft_state(project_id, iid, false).await {\n    Ok(()) => {}\n    Err(e) if e.to_string().contains(\"404\") => {\n        // MR gone; refresh MR list before retrying\n    }\n    Err(e) => return Err(e),\n}","handlingStrategy":"try-catch","validationCode":"let mr = client.get_merge_request(project_id, mr_iid).await?;\n// only toggle when state differs and MR is open\nif mr.state == \"opened\" && mr.draft != target_draft {\n    client.set_merge_request_draft_state(project_id, mr_iid, target_draft).await?;\n}","typeGuard":null,"tryCatchPattern":"match client.set_merge_request_draft_state(project_id, iid, true).await {\n    Ok(()) => {}\n    Err(e) if e.to_string().contains(\"404\") => eprintln!(\"MR no longer exists, skipping\"),\n    Err(e) if e.to_string().contains(\"401\") || e.to_string().contains(\"403\") => {\n        return Err(anyhow!(\"token lacks permission: {e}\"))\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Verify the MR iid belongs to the same project id used in the call","Ensure the token has the api scope and the user has at least Developer role","Check MR state == \"opened\" before toggling draft state","Retry transient statuses (429, 5xx) with exponential backoff"],"tags":["gitlab","http-error","draft-state","merge-request"],"backgroundTag":"http-error-response","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}