gitbutlerapp/gitbutler · error · anyhow::Error
Failed to fetch project: {status} - {error_text}
Error message
Failed to fetch project: {status} - {error_text} What it means
Thrown by GitLabClient::fetch_project_by_path (the impl behind fetch_project and fetch_project_by_numeric_id) when GET /projects/:id-or-path returns non-2xx. The message includes GitLab's response body, which for 404 says exactly what was not found. The function also computes the caller's effective access level from project/group permissions on success, so a failure here also blocks MR enrichment (enrich_merge_request_source_project calls it for the MR's source project).
Source
Thrown at crates/but-gitlab/src/client.rs:570
struct GitLabApiPermissions {
#[serde(default)]
project_access: Option<GitLabApiAccess>,
#[serde(default)]
group_access: Option<GitLabApiAccess>,
}
#[derive(Deserialize)]
struct GitLabApiAccess {
access_level: i64,
}
let url = format!("{}/projects/{}", self.base_url, project_id);
let response = self.client.get(&url).send().await?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
bail!("Failed to fetch project: {status} - {error_text}");
}
let project: GitLabApiProject = response.json().await?;
let access_level = project.permissions.and_then(|p| {
let project_level = p.project_access.map(|a| a.access_level);
let group_level = p.group_access.map(|a| a.access_level);
match (project_level, group_level) {
(Some(a), Some(b)) => Some(a.max(b)),
(Some(a), None) | (None, Some(a)) => Some(a),
(None, None) => None,
}
});
Ok(GitLabProject {
id: project.id,
path_with_namespace: project.path_with_namespace,
ssh_url_to_repo: project.ssh_url_to_repo,View on GitHub (pinned to caf1f223d3)
Solutions
- Confirm the project exists at https://<host>/<group>/<project> and copy the exact path (including subgroups)
- Read error_text: GitLab distinguishes `404 Project Not Found` from auth issues
- If a path with subgroups, pass it verbatim (group/sub/proj); numeric IDs are safer — take `id` from the project page
- Check the account still belongs to the project's group; re-run `but config forge auth` for 401
- If the project was transferred/renamed, update the stored project id in workspace metadata
Example fix
// before
let project = client.fetch_project(project_id).await?; // wrong-namespace path aborts everything
// after
let project = match client.fetch_project(project_id).await {
Ok(p) => p,
Err(err) if err.to_string().contains("404") => {
anyhow::bail!("project {project_id} not visible to this GitLab account — check namespace or access")
}
Err(err) => return Err(err),
}; Defensive patterns
Strategy: validation
Validate before calling
// Sanity-check the identifier shape before hitting the API
fn plausible_project_id(s: &str) -> bool {
!s.is_empty()
&& (s.chars().all(|c| c.is_ascii_digit()) // numeric id
|| !s.starts_with('/') && !s.ends_with('/') && !s.contains(' ') && !s.ends_with(".git"))
}
ensure!(plausible_project_id(project_id.as_str()), "malformed project id");
client.fetch_project(project_id).await?; Try / catch
match client.fetch_project(project_id).await {
Ok(p) => Ok(p),
Err(err) if err.to_string().contains("404") => bail!("project '{project_id}' not visible to this account"),
Err(err) => Err(err),
} Prevention
- Store numeric project ids (from the project page) instead of paths; they survive renames and transfers
- Keep the remote-URL parser's output free of '.git' suffixes and stray slashes
When it happens
Trigger: Project path with wrong namespace (`group/proj` typo → 404); numeric ID from a different GitLab instance (404); token lacks guest access so the project is invisible (404, GitLab hides forbidden projects); path not URL-encoded for slashes when the client does not encode it; archived/deleted project; expired token (401).
Common situations: Repo remote URL parsed into group/project incorrectly (extra `.git`, subgroups dropped); workspace config stores an old project ID after the project was transferred between groups; user removed from the group so every project fetch starts 404ing; fork's source project deleted.
Related errors
- Failed to get merge request: {}
- Failed to get MR merge status: {}
- Failed to update merge request: {status} - {error_text}
- Failed to merge merge request: {}
- Failed to set merge request draft state: {status} - {error_t
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/f6f6455f6ecd6f21.
Report an issue: GitHub.