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

  1. 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
  2. Check network/proxy reachability of api.bitbucket.org
  3. 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

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


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/bbf30b5914bd2aaf. Report an issue: GitHub.