Skip to content

JetBrains Runtime pagination stops on a page with no matching release class #1215

Description

@jdubois

Description:
JetBrainsDistribution.getAvailableVersions() stops paginating when the current GitHub releases page contains no releases matching the requested stability class.

The implementation first checks whether the raw page is empty, then filters that page by prerelease, and incorrectly treats an empty filtered result as end of pagination:

  • Pagination loop:
    // need to iterate through all pages to retrieve the list of all versions
    // GitHub API doesn't provide way to retrieve the count of pages to iterate so infinity loop
    let page_index = 1;
    const rawVersions: IJetBrainsRawVersion[] = [];
    const bearerToken = process.env.GITHUB_TOKEN;
    while (true) {
    const requestArguments = `per_page=100&page=${page_index}`;
    const requestHeaders: OutgoingHttpHeaders = {};
    if (bearerToken) {
    requestHeaders['Authorization'] = `Bearer ${bearerToken}`;
    }
    const rawUrl = `https://api.github.com/repos/JetBrains/JetBrainsRuntime/releases?${requestArguments}`;
    if (core.isDebug() && page_index === 1) {
    // url is identical except page_index so print it once for debug
    core.debug(`Gathering available versions from '${rawUrl}'`);
    }
    const paginationPageResult = (
    await this.http.getJson<IJetBrainsRawVersion[]>(rawUrl, requestHeaders)
    ).result;
    if (!paginationPageResult || paginationPageResult.length === 0) {
    // break infinity loop because we have reached end of pagination
    break;
    }
    const paginationPage: IJetBrainsRawVersion[] =
    paginationPageResult.filter(version =>
    this.stable ? !version.prerelease : version.prerelease
    );
    if (!paginationPage || paginationPage.length === 0) {
    // break infinity loop because we have reached end of pagination
    break;
    }
    rawVersions.push(...paginationPage);
    page_index++;
    }
  • Stability filter and early termination:
    const paginationPage: IJetBrainsRawVersion[] =
    paginationPageResult.filter(version =>
    this.stable ? !version.prerelease : version.prerelease
    );
    if (!paginationPage || paginationPage.length === 0) {
    // break infinity loop because we have reached end of pagination
    break;
    }

GitHub pagination is independent of that client-side filter. A non-empty page containing only prereleases is not the end of stable releases, and a non-empty page containing only stable releases is not the end of EA releases. Valid matching releases on later pages are therefore hidden.

Task version:
actions/setup-java at ab597f914a6894678251803485c09f413263b889 (main on 2026-08-05)

Platform:

  • Ubuntu
  • macOS
  • Windows

This occurs before platform-specific asset selection.

Runner type:

  • Hosted
  • Self-hosted

Repro steps:
Use deterministic mocked GitHub releases pages for GET /repos/JetBrains/JetBrainsRuntime/releases:

  1. For a stable request (java-version: 21, no -ea), return page 1 as:
    [{"tag_name":"jbr-release-26-ea","name":"jbr-release-26-ea","prerelease":true}]
    and page 2 as:
    [{"tag_name":"jbr-release-21.0.11b1163.116","name":"jbr-release-21.0.11b1163.116","prerelease":false}]
    End pagination after page 2.
  2. Call JetBrainsDistribution.getAvailableVersions().
  3. Observe that only page 1 is requested. Filtering page 1 produces [], so the loop breaks at lines 132-135 and never requests page 2.
  4. The reciprocal case reproduces for an EA request when page 1 contains only stable releases and page 2 contains prereleases.

These mocked page sequences are deterministic and isolate the pagination contract from the changing contents of the live JetBrains releases repository.

Expected behavior:
Pagination termination depends only on the raw GitHub page or GitHub's end-of-pagination signal. A non-empty raw page whose filtered result is empty must not prevent later pages from being fetched.

Actual behavior:
Pagination terminates when the filtered page is empty, so matching stable or EA releases on later pages are omitted and version resolution can fail with a misleading "No matching version found" error.

Acceptance criteria:

  • Continue pagination after a non-empty raw page with zero matching stable/EA releases.
  • Preserve stable/EA filtering and all existing JetBrains version and asset handling.
  • Terminate on GitHub's actual end-of-pagination signal.
  • Bound pagination with the repository's MAX_PAGINATION_PAGES safeguard.
  • Use the existing GitHub pagination/header and origin-validation helpers.
  • Add regression coverage for stable-after-prerelease and EA-after-stable page sequences, normal termination, and the pagination safeguard.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions