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

  1. Verify workspace/repo_slug against the repository's Bitbucket URL (slug casing matters)
  2. If 401/403, re-run 'but config forge auth' and ensure the token has read:repository scope
  3. Confirm the repo still exists and was not renamed or transferred
  4. 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

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


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/8842633f4a9feaa1. Report an issue: GitHub.