Skip to content

[Bug]: v3.0.2 release lookup can time out on repositories with large release metadata #829

Open

Description

@cxzhong

Pre-flight checks

  • I searched existing issues and did not find a duplicate
  • I reproduced this with the latest released version or current master
  • I included a reproducible example or a sanitized/redacted reproduction path if the original repository is private

action-gh-release version

v3.0.2 (softprops/action-gh-release@v3, resolving to 3d0d9888cb7fd7b750713d6e236d1fcb99157228 at the time of the failures)

Runner operating system

ubuntu-latest

Release target repository

sagemath/sage

Reproduction repo, gist, or artifact

Workflow snippet

- name: Create release
  uses: softprops/action-gh-release@v3
  with:
    preserve_order: true
    files: |
      dist/*
      upstream/*
    token: ${{ secrets.RELEASE_CREATION_TOKEN }}
    body_path: release_notes
    prerelease: ${{ contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc') }}
    max_retries: 3

max_retries is not a declared action input, so the warning about that input is understood and is not being reported as the cause. The workflow has since removed it.

Expected behavior

When the tag does not have a release yet, the action should create the release and upload the selected assets. A repository with many release assets should not make the missing-tag lookup time out.

Actual behavior

The Create release step fails before creating the release. In the latest run it ran for about 11 seconds (2026-07-26 20:54:40Z to 20:54:51Z), after all preceding jobs and artifact downloads had succeeded.

The public check annotation is:

We couldn't respond to your request in time. Sorry about that. Please try resubmitting your request and contact us if the problem persists.

The same workflow succeeded for 10.10.beta5 while @v3 resolved to v3.0.1, then failed for 10.10.beta6 and 10.10.beta7 after v3.0.2 was released. Pinning both Sage call sites to v3.0.1 is the current workaround: sagemath/sage#42599

Steps to reproduce

  1. Use v3.0.2 in a repository whose release-list response contains a large amount of embedded asset metadata, such as the public sagemath/sage repository.
  2. Trigger the action for a new tag that does not yet have a GitHub release.
  3. The direct getReleaseByTag request returns 404, after which v3.0.2 calls recentReleasesByTag.
  4. recentReleasesByTag scans up to two pages through allReleases, which fixes per_page: 100.
  5. The first release-list request can time out before release creation begins.

The API payload characteristic can be reproduced without modifying anything:

curl -sS -o /dev/null -w 'status=%{http_code} bytes=%{size_download} time=%{time_total}s\n' \
  'https://api.github.com/repos/sagemath/sage/releases?per_page=100&page=1'

Measured on 2026-07-30:

status=200 bytes=36612072 time=20.220455s

For comparison, a direct lookup of a nonexistent tag returned a 147-byte 404 in 0.96 seconds.

Relevant logs

Warning: Unexpected input(s) 'max_retries'
Error: We couldn't respond to your request in time. Sorry about that. Please try resubmitting your request and contact us if the problem persists.

The raw job log is not publicly downloadable without repository administration rights, so the exact timed-out HTTP route is inferred rather than proven from the log. The public annotation, v3.0.1/v3.0.2 boundary, and endpoint measurements strongly point to the new release-list fallback.

Additional context

PR #801 introduced the fallback scan so draft releases can still be found after the direct tag lookup returns 404: #801

Current master (5a6f51711ce2ba103b78f5e9550f810679f11e0e) retains the same path:

  • allReleases sets per_page: 100:
    allReleases(params: { owner: string; repo: string }): AsyncIterable<{ data: Release[] }> {
    const updatedParams = { per_page: 100, ...params };
    return this.github.paginate.iterator(
    this.github.rest.repos.listReleases.endpoint.merge(updatedParams),
    );
    }
  • the 404 fallback calls recentReleasesByTag:
    export async function findTagFromReleases(
    releaser: Releaser,
    owner: string,
    repo: string,
    tag: string,
    listingAttempts: number = 1,
    ): Promise<Release | undefined> {
    try {
    const { data: release } = await releaser.getReleaseByTag({ owner, repo, tag });
    return release;
    } catch (error) {
    if (getErrorStatus(error) !== 404) {
    throw error;
    }
    }
    if (listingAttempts <= 0) {
    return undefined;
    }
    const attempts = Math.max(1, listingAttempts);
    for (let attempt = 0; attempt < attempts; attempt += 1) {
    const recentReleases = await recentReleasesByTag(releaser, owner, repo, tag);
    const canonicalRelease = pickCanonicalRelease(recentReleases, undefined);
    if (canonicalRelease) {
    return canonicalRelease;
    }
    if (attempt < attempts - 1) {
    await sleep(CREATED_RELEASE_DISCOVERY_RETRY_DELAY_MS);
    }
    }
    return undefined;
    }
    const CREATED_RELEASE_DISCOVERY_RETRY_DELAY_MS = 1000;
    const RECENT_RELEASE_SCAN_PAGES = 2;
    async function sleep(ms: number): Promise<void> {
    await new Promise((resolve) => setTimeout(resolve, ms));
    }
    async function recentReleasesByTag(
    releaser: Releaser,
    owner: string,
    repo: string,
    tag: string,
    ): Promise<Release[]> {
    const matches: Release[] = [];
    let pages = 0;
    for await (const page of releaser.allReleases({ owner, repo })) {
    matches.push(...page.data.filter((release) => release.tag_name === tag));
    pages += 1;
    if (pages >= RECENT_RELEASE_SCAN_PAGES) {
    break;
    }
    }

This is related to the scalability concern in #724, but it is a different failure mode: the repository has far fewer than 10,000 releases; the response for 100 releases is large because it embeds metadata for many assets.

A metadata-only lookup (for example via GraphQL), or another approach that avoids one very large REST response, may preserve draft-release detection without this timeout. Simply creating after a list failure may risk duplicating an existing draft release, so that fallback would need care.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions