{"record":{"id":"9bdb7cc2a8954fd8","repo":"gitbutlerapp/gitbutler","slug":"failed-to-get-mr-merge-status","errorCode":null,"errorMessage":"Failed to get MR merge status: {}","messagePattern":"Failed to get MR merge status: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-gitlab/src/client.rs","lineNumber":349,"sourceCode":"        &self,\n        project_id: GitLabProjectId,\n        mr_iid: i64,\n    ) -> Result<MergeRequestMergeStatus> {\n        #[derive(Debug, Deserialize)]\n        struct MrMergeStatusResponse {\n            #[serde(default)]\n            merge_status: Option<String>,\n            #[serde(default)]\n            user_notes_count: i64,\n        }\n\n        let url = format!(\n            \"{}/projects/{}/merge_requests/{}\",\n            self.base_url, project_id, mr_iid\n        );\n        let response = self.client.get(&url).send().await?;\n        if !response.status().is_success() {\n            bail!(\"Failed to get MR merge status: {}\", response.status());\n        }\n        let body: MrMergeStatusResponse = response.json().await?;\n        let is_mergeable = matches!(body.merge_status.as_deref(), Some(\"can_be_merged\"));\n        Ok(MergeRequestMergeStatus {\n            mergeable_state: body.merge_status,\n            comments_count: body.user_notes_count,\n            is_mergeable,\n        })\n    }\n\n    pub async fn update_merge_request(\n        &self,\n        params: &UpdateMergeRequestParams<'_>,\n    ) -> Result<MergeRequest> {\n        #[derive(Serialize)]\n        struct UpdateMergeRequestBody<'a> {\n            #[serde(skip_serializing_if = \"Option::is_none\")]\n            title: Option<&'a str>,","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-gitlab/src/client.rs#L331-L367","documentation":"Thrown by GitLabClient::get_mr_merge_status when GET /projects/:id/merge_requests/:iid (the same endpoint used for full MR fetches) returns a non-2xx status while probing only the merge_status and user_notes_count fields. It exists as a cheap pre-check before attempting a merge: is_mergeable is true only when GitLab reports merge_status == \"can_be_merged\". Like the other status bails in this client, only the status code is reported, not GitLab's error body.","triggerScenarios":"Calling get_mr_merge_status(project_id, mr_iid) for an MR that was deleted or is not visible to the token (404), a forbidden project (403), an unauthenticated request (401), or passing mr_iid = 0 / a negative value that GitLab rejects.","commonSituations":"UI polls merge status on a timer for an MR that got closed+deleted while the poll was in flight; token lost permissions when the user was removed from the project; stale MR list cached locally referencing deleted MRs.","solutions":["Verify the MR still exists (browser or curl the same endpoint) before polling status","Re-run `but config forge auth` if the token expired (401)","Stop or debounce the polling loop once an MR is detected as closed/merged so it does not query deleted MRs","If 403, confirm the account still has access to the project"],"exampleFix":"// before\nlet status = client.get_mr_merge_status(project_id, mr_iid).await?; // error bubbles into the whole view\n\n// after\nlet status = match client.get_mr_merge_status(project_id, mr_iid).await {\n    Ok(s) => s,\n    Err(err) if err.to_string().contains(\"404\") => {\n        MergeRequestMergeStatus { mergeable_state: None, comments_count: 0, is_mergeable: false } // MR gone; not mergeable\n    }\n    Err(err) => return Err(err),\n};","handlingStrategy":"try-catch","validationCode":"// Only poll status for MRs you know exist (from a prior list/get)\nensure!(known_mr_ids.contains(&mr_iid), \"skipping status poll for unknown MR {mr_iid}\");\nlet status = client.get_mr_merge_status(project_id, mr_iid).await?;","typeGuard":null,"tryCatchPattern":"let mergeable = match client.get_mr_merge_status(project_id, mr_iid).await {\n    Ok(s) => s.is_mergeable,\n    Err(_) => false, // unknown state is not mergeable; never enable merge actions on it\n};","preventionTips":["Drive merge-button enablement from is_mergeable instead of assuming, so 405 merges never happen","Debounce or stop status polling once an MR leaves the opened state"],"tags":["gitlab","http","merge-status","merge-request","rust"],"backgroundTag":"gitlab-api-error","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}