{"record":{"id":"f35955e1e819b23c","repo":"gitbutlerapp/gitbutler","slug":"failed-to-decline-pull-request-status-error","errorCode":null,"errorMessage":"Failed to decline pull request: {status} - {error_text}","messagePattern":"Failed to decline pull request: (.+?) - (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-bitbucket/src/client.rs","lineNumber":484,"sourceCode":"    /// Decline (close) a pull request.\n    pub async fn decline_pull_request(\n        &self,\n        workspace: &str,\n        repo_slug: &str,\n        id: i64,\n    ) -> Result<()> {\n        let url = format!(\n            \"{}/repositories/{}/{}/pullrequests/{}/decline\",\n            self.base_url,\n            urlencoding::encode(workspace),\n            urlencoding::encode(repo_slug),\n            id,\n        );\n        let response = self.client.post(&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 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?;","sourceCodeStart":466,"sourceCodeEnd":502,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-bitbucket/src/client.rs#L466-L502","documentation":"Thrown by decline_pull_request when the HTTP POST that declines (closes without merging) a Bitbucket pull request returns a non-success status. The message includes the HTTP status and Bitbucket's error body. Most commonly the PR is no longer in a state that can be declined.","triggerScenarios":"Declining a PR that was already merged or already declined (400/409), wrong PR id (404), or a token lacking write access (403/401).","commonSituations":"Race with another teammate who merged/closed the PR first; stale cached PR id; automation retrying decline on an already-closed PR; bot token with read-only scope.","solutions":["Fetch the PR's current state first and skip declining if already merged/declined","Verify the PR id is correct and the PR is still open","Check the token has write permission on the repository","Treat 'already closed' style error bodies as success in idempotent automation"],"exampleFix":"// before\nclient.decline_pull_request(workspace, repo_slug, id).await?;\n// after\nmatch client.decline_pull_request(workspace, repo_slug, id).await {\n    Ok(()) => {},\n    Err(e) if e.to_string().contains(\"400\") => { /* already merged/declined; ignore */ },\n    Err(e) => return Err(e),\n}","handlingStrategy":"try-catch","validationCode":"let pr_state = get_pr_state(ws, slug, id)?;\nanyhow::ensure!(pr_state == \"OPEN\", \"PR #{id} already closed; skip decline\");","typeGuard":null,"tryCatchPattern":"match client.decline_pull_request(ws, slug, id).await {\n    Err(e) if e.to_string().contains(\"400\") || e.to_string().contains(\"409\") => { /* already merged/declined */ },\n    other => other?,\n}","preventionTips":["Re-fetch PR state before declining in retries","Treat decline as idempotent in automation","Use write-scoped tokens","Avoid stale cached PR ids"],"tags":["network","http","bitbucket","pull-request"],"backgroundTag":"upstream-api-error","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}