gitbutlerapp/gitbutler · error · anyhow::Error
Bitbucket request failed: {}
Error message
Bitbucket request failed: {} What it means
Raised inside get_paginated() when any page of a Bitbucket Cloud API request returns a non-2xx HTTP status; the message embeds the reqwest StatusCode. Because it fires mid-pagination, earlier pages may already have been collected but are discarded when this error propagates.
Source
Thrown at crates/but-bitbucket/src/client.rs:106
}
/// Fetch every `values` entry across a paginated Bitbucket collection,
/// following the `next` cursor URL until exhausted. Errors out if the
/// `MAX_PAGES` safety cap is hit rather than silently truncating the result.
async fn get_paginated<T: DeserializeOwned>(&self, initial_url: String) -> Result<Vec<T>> {
let mut items = Vec::new();
let mut next = Some(initial_url);
let mut pages = 0;
while let Some(url) = next.take() {
if pages >= MAX_PAGES {
bail!("Bitbucket pagination exceeded the {MAX_PAGES}-page safety cap");
}
pages += 1;
let response = self.client.get(&url).send().await?;
if !response.status().is_success() {
bail!("Bitbucket request failed: {}", response.status());
}
let page: Paginated<T> = response.json().await?;
items.extend(page.values);
next = page.next;
}
Ok(items)
}
/// Fetch a single page of a Bitbucket collection without following the
/// `next` cursor. Used for "most recent" listings where the caller sorts
/// server-side and only needs the head of the result set.
async fn get_first_page<T: DeserializeOwned>(&self, url: String) -> Result<Vec<T>> {
let response = self.client.get(&url).send().await?;
if !response.status().is_success() {
bail!("Bitbucket request failed: {}", response.status());
}
let page: Paginated<T> = response.json().await?;View on GitHub (pinned to caf1f223d3)
Solutions
- Classify on the embedded status: 401/403 -> re-run 'but config forge auth' and retry; 404 -> verify workspace and repo slug spelling; 429/5xx -> retry with exponential backoff
- Check network/proxy reachability of api.bitbucket.org
- If the failure happens after page 1, suspect a token revoked mid-walk or rate limiting and restart the full listing after recovery
Defensive patterns
Strategy: retry
Validate before calling
null // no cheap pre-check for arbitrary server statuses; validate auth instead let accounts = but_bitbucket::token::list_known_bitbucket_accounts(storage)?; anyhow::ensure!(!accounts.is_empty(), "authenticate first via 'but config forge auth'");
Try / catch
// classify the embedded status, retry only transient classes
for attempt in 0..5 {
match client.list_open_prs(ws, slug).await {
Err(e) if status_of(&e).is_some_and(|s| s.as_u16() == 429 || s.is_server_error()) => {
tokio::time::sleep(backoff(attempt)).await; continue;
}
r => break r,
}
}? Prevention
- Refresh tokens proactively before long batch walks
- Verify workspace/repo slug at integration setup to avoid mid-walk 404s
- Wrap paginated calls with retry-on-429/5xx and give up fast on 4xx
When it happens
Trigger: Calling a paginated Bitbucket listing (open PRs, build statuses, ...) that gets a 401 (expired/revoked token), 403 (missing scope), 404 (wrong workspace/repo slug), 429 (rate limit), or 5xx from api.bitbucket.org while following the 'next' cursor.
Common situations: Long-lived CLI/desktop sessions whose OAuth token expired between pages; tokens created without read:pullrequest scope; typos in repo slug; Bitbucket Cloud incidents or rate limiting during bulk listing.
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 create pull request: {status} - {error_text}
- Failed to update pull request: {status} - {error_text}
- Failed to merge pull request: {status} - {error_text}
- Failed to decline pull request: {status} - {error_text}
- Failed to fetch repository: {status} - {error_text}
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/bbf30b5914bd2aaf.
Report an issue: GitHub.