gitbutlerapp/gitbutler · error · HttpStatusError
Failed to create pull request
Error message
Failed to create pull request: {status} - {error_text} What it means
Raised by the Bitbucket API client when a POST to create a pull request returns a non-2xx status. The error wraps `HttpStatusError` with a context string containing the HTTP status and the raw response body, so the caller sees exactly why Bitbucket rejected the PR creation.
Solutions
- Read the `error_text` in the context message — Bitbucket includes the field-level validation reason there.
- Verify the token has the `write:pullrequest:bitbucket` scope and has not expired.
- Confirm the workspace/repo slug and that both the source and destination branches exist on the remote.
- If the status is 429 or 5xx, wait and retry the PR creation.
Example fix
// before
match client.create_pull_request(&title, &head, &base).await {
Err(e) => panic!("{e}"),
Ok(pr) => println!("{}", pr.url),
}
// after
match client.create_pull_request(&title, &head, &base).await {
Err(e) if e.to_string().contains("400") => eprintln!("Invalid PR payload: {e:#}"),
Err(e) => eprintln!("PR creation failed: {e:#}"),
Ok(pr) => println!("{}", pr.url),
} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight: verify token can see the repo and branches exist let head_ok = client.repo_exists(workspace, repo_slug).await?; let base_ok = client.branch_exists(workspace, repo_slug, base).await?;
Try / catch
match client.create_pull_request(&title, &head, &base).await {
Err(e) if e.to_string().contains("429") => backoff_and_retry().await,
Err(e) => log::error!("PR create failed: {e:#}"),
Ok(pr) => Ok(pr),
} Prevention
- Grant the token the `write:pullrequest:bitbucket` scope.
- Validate branch names and workspace/repo slugs before calling.
- Handle 429/5xx with exponential backoff in CI.
When it happens
Trigger: Calling `create_pull_request` -> `send_pr_create` when Bitbucket responds with 400 (invalid PR payload), 401/403 (bad token or insufficient scope), 404 (wrong workspace/repo slug), or 429/5xx (server-side issues).
Common situations: Stale or revoked Bitbucket app password/API token; missing `write:pullrequest:bitbucket` scope; typo'd workspace or repo slug; source or destination branch name that does not exist or contains invalid characters; Bitbucket rate limiting during CI bursts.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- Failed to decline pull request
- Failed to get PR base repo
- Failed to merge pull request
- Failed to update pull request
- Failed to verify Bitbucket repository access
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/247f6e1566cbea39.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-bitbucket/src/client.rs:275
close_source_branch: true,
source,
destination: DestinationBody {
branch: BranchBody {
name: params.target_branch,
},
},
reviewers: reviewer_account_ids
.iter()
.map(|account_id| ReviewerBody { account_id })
.collect(),
};
let response = self.client.post(&url).json(&body).send().await?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
return Err(
anyhow::Error::new(HttpStatusError { status }).context(format!(
"Failed to create pull request: {status} - {error_text}"
)),
);
}
let pr: BitbucketApiPullRequest = response.json().await?;
Ok(pr.into())
}
/// Bitbucket does not apply default reviewers to API-created pull requests,
/// so fetch the effective set (repository- plus project-level) minus the
/// author, whom Bitbucket rejects as a reviewer.
/// Best-effort: a failed lookup yields no reviewers.
async fn default_reviewers_for_new_pr(&self, workspace: &str, repo_slug: &str) -> Vec<String> {
#[derive(Deserialize)]
struct DefaultReviewer {
#[serde(default)]
user: Option<BitbucketApiUser>,
}View on GitHub (pinned to 58e5313667)