{"record":{"id":"9726f20444f6c391","repo":"gitbutlerapp/gitbutler","slug":"failed-to-get-merge-request","errorCode":null,"errorMessage":"Failed to get merge request: {}","messagePattern":"Failed to get merge request: (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-gitlab/src/client.rs","lineNumber":308,"sourceCode":"\n        let mr: GitLabMergeRequest = response.json().await?;\n        Ok(mr.into())\n    }\n\n    pub async fn get_merge_request(\n        &self,\n        project_id: GitLabProjectId,\n        mr_iid: i64,\n    ) -> Result<MergeRequest> {\n        let url = format!(\n            \"{}/projects/{}/merge_requests/{}\",\n            self.base_url, project_id, mr_iid\n        );\n\n        let response = self.client.get(&url).send().await?;\n\n        if !response.status().is_success() {\n            bail!(\"Failed to get merge request: {}\", response.status());\n        }\n\n        let mr: GitLabMergeRequest = response.json().await?;\n        let mr = mr.into();\n        match self.enrich_merge_request_source_project(mr).await {\n            Ok(mr) => Ok(mr),\n            Err(err) => {\n                tracing::warn!(\n                    error = ?err.source(),\n                    project_id = %project_id,\n                    mr_iid,\n                    \"Failed to enrich GitLab merge request source project\"\n                );\n                Ok(err.into_inner())\n            }\n        }\n    }\n","sourceCodeStart":290,"sourceCodeEnd":326,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-gitlab/src/client.rs#L290-L326","documentation":"Thrown by GitLabClient::get_merge_request when GitLab's REST API answers GET /projects/:id/merge_requests/:iid with any non-2xx status. Only the status code is embedded; the response body is discarded, so you must map 401/403/404 yourself. Network/transport failures of the same call surface as a different error (the `?` on send()), and a JSON decode failure as yet another; this bail is strictly an HTTP-level rejection. On success the MR is additionally enriched with its source project (fork detection, SSH/HTTPS URLs), which can also fail but is only warned about, not propagated.","triggerScenarios":"Calling get_merge_request(project_id, mr_iid) with an MR iid that does not exist (404), a project id the token cannot read (403 or 404), an expired/revoked personal access token (401), or a base_url pointing at the wrong GitLab instance where the project id does not resolve.","commonSituations":"Token expired between sessions; MR was merged and the source branch/project (fork) deleted, leaving a stale iid in local state; numeric project ID vs URL-encoded namespace/path confusion; self-hosted GitLab on an old version lacking the endpoint; user logged out or revoked the app/token.","solutions":["Re-authenticate: run `but config forge auth` to refresh the GitLab token, then retry","Verify the pair exists: open https://<host>/<project>/-/merge_requests/<iid> in a browser, or curl GET /api/v4/projects/<id>/merge_requests/<iid> with the same token","Check the token's scopes (needs `api` or `read_api`) and that it belongs to an account with at least Reporter access to the project","Confirm the configured GitLab host/base_url and that project_id matches that instance (not a numeric ID from another host)","If the MR was deleted, invalidate the cached MR reference in your data so the UI stops requesting it"],"exampleFix":"// before\nlet mr = client.get_merge_request(project_id, mr_iid).await?; // aborts the whole flow on a 404\n\n// after\nlet mr = match client.get_merge_request(project_id, mr_iid).await {\n    Ok(mr) => Some(mr),\n    Err(err) if err.to_string().contains(\"404\") => {\n        tracing::warn!(%mr_iid, \"GitLab MR no longer exists, dropping from cache\");\n        None // treat as deleted instead of failing\n    }\n    Err(err) => return Err(err),\n};","handlingStrategy":"try-catch","validationCode":"// Confirm the project is visible with the same token before MR lookups\nclient.fetch_project(project_id.clone()).await?; // surfaces auth/project problems first\nlet mr = client.get_merge_request(project_id, mr_iid).await?;","typeGuard":null,"tryCatchPattern":"match client.get_merge_request(project_id, mr_iid).await {\n    Ok(mr) => Ok(Some(mr)),\n    Err(err) if err.to_string().contains(\"404\") => Ok(None), // MR deleted/invisible: not fatal\n    Err(err) => Err(err.context(\"fetch GitLab merge request\")),\n}","preventionTips":["Invalidate cached MR ids when the UI observes the MR as closed/merged/deleted","Wrap status-code errors: the client bails with only the status string, so classify on '401'/'403'/'404' substrings until typed errors exist","Refresh the GitLab token via `but config forge auth` whenever a 401 appears anywhere in the session"],"tags":["gitlab","http","merge-request","api-client","rust"],"backgroundTag":"gitlab-api-error","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}