gitbutlerapp/gitbutler · error · anyhow::Error

Bitbucket pagination exceeded the {MAX_PAGES}-page safety ca

Error message

Bitbucket pagination exceeded the {MAX_PAGES}-page safety cap

What it means

get_paginated() in but-bitbucket follows the 'next' cursor of a Bitbucket collection and hard-caps the walk at MAX_PAGES (25, defined at crates/but-bitbucket/src/client.rs:11). Hitting the cap raises this error instead of silently returning a truncated list, so callers never mistake a partial result for the full set.

Source

Thrown at crates/but-bitbucket/src/client.rs:100

        Ok(AuthenticatedUser {
            username: user.username(),
            avatar_url: user.avatar_url(),
            name: user.display_name,
            account_id: user.account_id,
        })
    }

    /// 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.

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Narrow the server-side filter (state=OPEN, narrower repo/workspace scope, date filters) so the result set fits in 25 pages
  2. If you only need the newest entries, use the single-page path (get_first_page) instead of full pagination
  3. If you genuinely must fetch more, raise the MAX_PAGES constant in crates/but-bitbucket/src/client.rs and rebuild, accepting the longer run time
  4. Log the last visited URL to check whether the 'next' cursor is looping instead of advancing (server-side pagination bug)

Example fix

// before: every open PR, can exceed the 25-page cap on huge repos
let prs = client.list_open_prs(ws, slug).await?;

// after: only the most recent page is needed for 'latest PR' views
let prs = client.list_recent_prs_first_page(ws, slug).await?; // get_first_page-based
Defensive patterns

Strategy: fallback

Validate before calling

// estimate up front: you cannot cheaply count pages, but you can narrow the query
let url = format!("{base}/repositories/{ws}/{slug}/pullrequests?state=OPEN&pagelen=50&q=updated_on>={week_ago}");

Try / catch

// degrade gracefully instead of failing the whole feature
match client.list_all_build_statuses(ws, slug, sha).await {
    Err(e) if e.to_string().contains("safety cap") => client.list_first_page_statuses(ws, slug, sha).await,
    r => r,
}

Prevention

When it happens

Trigger: Calling a Bitbucket listing that uses get_paginated (PRs, build statuses, etc.) on a workspace/repo whose collection spans more than 25 pages — e.g. more than ~1250 open pull requests at pagelen=50 — or when a misbehaving server keeps returning a 'next' URL that never drains.

Common situations: Large monorepos or org accounts with thousands of PRs/build statuses; CI noise generating many build-status pages; a stale 'next' cursor after API changes.

Related errors


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