{"record":{"id":"247f6e1566cbea39","repo":"gitbutlerapp/gitbutler","slug":"failed-to-create-pull-request-status-error-t","errorCode":null,"errorMessage":"Failed to create pull request: {status} - {error_text}","messagePattern":"Failed to create pull request: (.+?) - (.+?)","errorType":"http","errorClass":"HttpStatusError","httpStatus":null,"severity":"error","filePath":"crates/but-bitbucket/src/client.rs","lineNumber":249,"sourceCode":"            close_source_branch: true,\n            source,\n            destination: DestinationBody {\n                branch: BranchBody {\n                    name: params.target_branch,\n                },\n            },\n            reviewers: reviewer_account_ids\n                .iter()\n                .map(|account_id| ReviewerBody { account_id })\n                .collect(),\n        };\n\n        let response = self.client.post(&url).json(&body).send().await?;\n        if !response.status().is_success() {\n            let status = response.status();\n            let error_text = response.text().await.unwrap_or_default();\n            return Err(\n                anyhow::Error::new(HttpStatusError { status }).context(format!(\n                    \"Failed to create pull request: {status} - {error_text}\"\n                )),\n            );\n        }\n        let pr: BitbucketApiPullRequest = response.json().await?;\n        Ok(pr.into())\n    }\n\n    /// Bitbucket does not apply default reviewers to API-created pull requests,\n    /// so fetch the effective set (repository- plus project-level) minus the\n    /// author, whom Bitbucket rejects as a reviewer.\n    /// Best-effort: a failed lookup yields no reviewers.\n    async fn default_reviewers_for_new_pr(&self, workspace: &str, repo_slug: &str) -> Vec<String> {\n        #[derive(Deserialize)]\n        struct DefaultReviewer {\n            #[serde(default)]\n            user: Option<BitbucketApiUser>,\n        }","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/2497b8007aa4a1922dae9a805b32ffe5b5037785/crates/but-bitbucket/src/client.rs#L231-L267","documentation":"Raised by but-bitbucket's pull-request creation: the POST to Bitbucket Cloud's /2.0/repositories/{workspace}/{repo_slug}/pullrequests answered a non-success HTTP status. The error wraps HttpStatusError with the status code plus Bitbucket's raw response body (error_text), which names the exact violation. Note the client already retries once with an empty reviewer list when a 400 was caused by default reviewers, so a surfaced failure usually has another cause (scope, duplicate PR, credentials).","triggerScenarios":"Calling the PR-creating flow with a token lacking the pullrequest:write scope (403); an open pull request already exists for the same source branch -> target branch pair (400); expired or invalid credentials (401); wrong workspace or repo_slug (404); a reviewer rejected for a reason other than being the author (400); API rate limiting (429).","commonSituations":"Workspace-scoped API key created with read-only scopes; re-running a create-PR command that already succeeded; copying the repo slug with wrong casing; project-level default reviewers that include deactivated accounts.","solutions":["Read the {error_text} segment of the message first - Bitbucket names the offending field (e.g. reviewers or source branch)","Recreate the token with the pullrequest:write scope (plus read:user:bitbucket for reviewer lookup) and re-add it","Check whether an open PR for the same source/target pair already exists and reuse or close it before creating another","Verify workspace and repo_slug against the browser URL (bitbucket.org/<workspace>/<repo_slug>)","On 429/5xx, honor Retry-After and re-run the command after a pause"],"exampleFix":"// before\nif !response.status().is_success() {\n    let status = response.status();\n    let error_text = response.text().await.unwrap_or_default();\n    return Err(anyhow::Error::new(HttpStatusError { status })\n        .context(format!(\"Failed to create pull request: {status} - {error_text}\")));\n}\n\n// after - classify the frequent 400 duplicate-PR case into an actionable message\nif status == reqwest::StatusCode::BAD_REQUEST && error_text.contains(\"already\") {\n    return Err(anyhow!(\"An open pull request for this source/target pair already exists\"));\n}","handlingStrategy":"try-catch","validationCode":"// Before creating, confirm no open PR exists for the same source/target pair\nlet open = client\n    .get(format!(\"{base}/repositories/{ws}/{slug}/pullrequests?state=OPEN&q=source.branch.name=\\\"{src}\\\"+AND+destination.branch.name=\\\"{dst}\\\"\"))\n    .send().await?\n    .json::<BitbucketPage>().await?;\nif !open.values.is_empty() {\n    anyhow::bail!(\"an open pull request for {src} -> {dst} already exists\");\n}","typeGuard":"fn is_http_status_error(err: &anyhow::Error) -> bool {\n    err.downcast_ref::<HttpStatusError>().is_some()\n}","tryCatchPattern":"match client.create_pull_request(&params).await {\n    Ok(pr) => { /* store pr.id */ }\n    Err(err) => {\n        if let Some(e) = err.downcast_ref::<HttpStatusError>() {\n            match e.status {\n                reqwest::StatusCode::FORBIDDEN => /* guide user to add pullrequest:write scope */,\n                reqwest::StatusCode::UNAUTHORIZED => /* prompt re-auth */,\n                reqwest::StatusCode::NOT_FOUND => /* re-check workspace/repo_slug */,\n                s if s.is_client_error() => /* surface err's context: status + error_text */,\n                _ => /* transient: schedule retry with backoff */,\n            }\n        } else {\n            return Err(err);\n        }\n    }\n}","preventionTips":["Create tokens with a written scope checklist (pullrequest:write, read:user, read:repository) before first use","Make PR-creation scripts idempotent: query open PRs for the pair before POSTing","Log the full error chain (status plus Bitbucket's error body) so reruns are diagnosable","Respect Retry-After on 429 instead of immediately retrying"],"tags":["bitbucket","pull-request","http-status","api","rust","oauth-scope"],"backgroundTag":"http-api-error-response","analyzedSha":"2497b8007aa4a1922dae9a805b32ffe5b5037785","analyzedAt":"2026-08-17T00:30:25.648Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}