{"record":{"id":"8842633f4a9feaa1","repo":"gitbutlerapp/gitbutler","slug":"failed-to-fetch-repository-status-error-text","errorCode":null,"errorMessage":"Failed to fetch repository: {status} - {error_text}","messagePattern":"Failed to fetch repository: (.+?) - (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-bitbucket/src/client.rs","lineNumber":474,"sourceCode":"            let status = response.status();\n            let error_text = response.text().await.unwrap_or_default();\n            bail!(\"Failed to decline pull request: {status} - {error_text}\");\n        }\n        Ok(())\n    }\n\n    pub async fn fetch_repo(&self, workspace: &str, repo_slug: &str) -> Result<BitbucketRepo> {\n        let url = format!(\n            \"{}/repositories/{}/{}\",\n            self.base_url,\n            urlencoding::encode(workspace),\n            urlencoding::encode(repo_slug),\n        );\n        let response = self.client.get(&url).send().await?;\n        if !response.status().is_success() {\n            let status = response.status();\n            let error_text = response.text().await.unwrap_or_default();\n            bail!(\"Failed to fetch repository: {status} - {error_text}\");\n        }\n        let repo: BitbucketApiRepository = response.json().await?;\n\n        // The caller's permission level lives behind a separate endpoint.\n        let permission = self\n            .fetch_repo_permission(workspace, repo_slug)\n            .await\n            .ok()\n            .flatten();\n\n        Ok(BitbucketRepo {\n            is_fork: repo.parent.is_some(),\n            permission,\n        })\n    }\n\n    /// Resolve the authenticated user's permission (`admin`/`write`/`read`) on a repo.\n    async fn fetch_repo_permission(","sourceCodeStart":456,"sourceCodeEnd":492,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-bitbucket/src/client.rs#L456-L492","documentation":"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().","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// verify slug shape and auth before the fetch\nanyhow::ensure!(!workspace.is_empty() && !repo_slug.is_empty()\n    && !repo_slug.ends_with(\".git\"), \"check workspace/repo_slug\");\nanyhow::ensure!(!but_bitbucket::token::list_known_bitbucket_accounts(storage)?.is_empty(),\n    \"run 'but config forge auth' first\");","typeGuard":null,"tryCatchPattern":"match client.fetch_repo(ws, slug).await {\n    Err(e) => {\n        let m = e.to_string();\n        if m.contains(\"404\") { return Err(e.context(\"wrong workspace or repo slug?\")); }\n        if m.contains(\"401\") || m.contains(\"403\") { return Err(e.context(\"token lacks read:repository scope\")); }\n        Err(e)\n    }\n    ok => ok,\n}","preventionTips":["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"],"tags":["bitbucket","repository","http","forge"],"backgroundTag":"repository-access-denied","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}