gitbutlerapp/gitbutler · error · anyhow::Error
Failed to decline pull request: {status} - {error_text}
Error message
Failed to decline pull request: {status} - {error_text} What it means
Raised when POSTing to /repositories/{workspace}/{repo_slug}/pullrequests/{id}/decline returns non-2xx; the message embeds the status and the raw response body. Declining is only valid on an OPEN pull request with a token that has pullrequest write permission.
Source
Thrown at crates/but-bitbucket/src/client.rs:458
/// Decline (close) a pull request.
pub async fn decline_pull_request(
&self,
workspace: &str,
repo_slug: &str,
id: i64,
) -> Result<()> {
let url = format!(
"{}/repositories/{}/{}/pullrequests/{}/decline",
self.base_url,
urlencoding::encode(workspace),
urlencoding::encode(repo_slug),
id,
);
let response = self.client.post(&url).send().await?;
if !response.status().is_success() {
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?;View on GitHub (pinned to caf1f223d3)
Solutions
- Read {error_text} for the concrete reason; state errors mention the current PR state
- Re-fetch the PR first and only decline when values.state == OPEN
- Re-authenticate ('but config forge auth') if 401/403 so the token gains write scope
- Treat decline-of-already-closed PRs as a no-op success in automation
Defensive patterns
Strategy: try-catch
Validate before calling
let pr = client.get_pull_request(ws, slug, id).await?;
anyhow::ensure!(pr.state == "OPEN", "PR {id} already {}", pr.state); Try / catch
match client.decline_pull_request(ws, slug, id).await {
Err(e) if e.to_string().contains("409") => Ok(()), // already closed — desired end state reached
r => r,
} Prevention
- Treat decline as idempotent in automation: refresh state on 409 instead of failing
- Filter lists to OPEN before offering bulk-decline actions
- Re-check state after any long-running workflow before declining
When it happens
Trigger: Declining a PR that is already MERGED (Bitbucket refuses to decline merged PRs), already DECLINED (409 state conflict), a deleted PR (404), or using a token without write scope (401/403).
Common situations: 'Close all my PRs' batch scripts that race with auto-merge; stale UI views letting users decline an already-merged PR; read-only service tokens used for lifecycle operations.
Related errors
- Failed to update pull request: {status} - {error_text}
- Failed to merge pull request: {status} - {error_text}
- Failed to create pull request: {status} - {error_text}
- Bitbucket request failed: {}
- Failed to fetch repository: {status} - {error_text}
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/f35955e1e819b23c.
Report an issue: GitHub.