gitbutlerapp/gitbutler · error · anyhow::Error
Failed to fetch repository: {status} - {error_text}
Error message
Failed to fetch repository: {status} - {error_text} What it means
Raised by fetch_repo() when GET /repositories/{workspace}/{repo_slug} returns non-2xx; the message includes the status and Bitbucket's response body. Note this plain bail fires before the nicer permission classification — the permission sub-fetch (fetch_repo_permission) is best-effort and its failure is swallowed with .ok().
Source
Thrown at crates/but-bitbucket/src/client.rs:474
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
bail!("Failed to decline pull request: {status} - {error_text}");
}
Ok(())
}
pub async fn fetch_repo(&self, workspace: &str, repo_slug: &str) -> Result<BitbucketRepo> {
let url = format!(
"{}/repositories/{}/{}",
self.base_url,
urlencoding::encode(workspace),
urlencoding::encode(repo_slug),
);
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 repository: {status} - {error_text}");
}
let repo: BitbucketApiRepository = response.json().await?;
// The caller's permission level lives behind a separate endpoint.
let permission = self
.fetch_repo_permission(workspace, repo_slug)
.await
.ok()
.flatten();
Ok(BitbucketRepo {
is_fork: repo.parent.is_some(),
permission,
})
}
/// Resolve the authenticated user's permission (`admin`/`write`/`read`) on a repo.
async fn fetch_repo_permission(View on GitHub (pinned to caf1f223d3)
Solutions
- Verify workspace/repo_slug against the repository's Bitbucket URL (slug casing matters)
- If 401/403, re-run 'but config forge auth' and ensure the token has read:repository scope
- Confirm the repo still exists and was not renamed or transferred
- Check the response body in the message for Bitbucket's exact error detail
Defensive patterns
Strategy: validation
Validate before calling
// verify slug shape and auth before the fetch
anyhow::ensure!(!workspace.is_empty() && !repo_slug.is_empty()
&& !repo_slug.ends_with(".git"), "check workspace/repo_slug");
anyhow::ensure!(!but_bitbucket::token::list_known_bitbucket_accounts(storage)?.is_empty(),
"run 'but config forge auth' first"); Try / catch
match client.fetch_repo(ws, slug).await {
Err(e) => {
let m = e.to_string();
if m.contains("404") { return Err(e.context("wrong workspace or repo slug?")); }
if m.contains("401") || m.contains("403") { return Err(e.context("token lacks read:repository scope")); }
Err(e)
}
ok => ok,
} Prevention
- Copy slugs from the repo's canonical URL, never from clone strings with .git
- Run repo fetch once at integration setup to fail fast on bad slugs
- Keep forge integration config validated after workspace renames
When it happens
Trigger: Fetching a repository with a wrong workspace or repo_slug (404), a private repo the token cannot see (401/403), a deleted/transferred repo, or a token missing read:repository scope.
Common situations: Forge integration configured with a slug copied from the browser URL that includes '.git' or wrong casing; workspace renamed after the integration was set up; token scopes trimmed; repo transferred to another workspace.
Related errors
- Failed to create pull request: {status} - {error_text}
- Failed to verify Bitbucket repository access
- Bitbucket pagination exceeded the {MAX_PAGES}-page safety ca
- Bitbucket request failed: {}
- Failed to update pull request: {status} - {error_text}
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/8842633f4a9feaa1.
Report an issue: GitHub.