Skip to content

Bug: Release notes baseline picks chronologically newest release instead of semantically latest version #88

@joshjohanning

Description

@joshjohanning

Bug Description

When generating release notes, the action determines the "previous release" to use as the baseline for the changelog. Currently, it picks the most recently created release with a semver tag — but this is wrong when hotpatches are published to older major/minor lines.

Reproduction Scenario

Using the approveops repo as a real-world example:

  1. v4.0.0 is the latest release (marked LATEST on the GitHub Releases page)
  2. A hotpatch v2.0.6 is published to the v2 branch (for users still on v2)
  3. When v4.0.1 is later published, the action generates release notes comparing v2.0.6v4.0.1 instead of the correct v4.0.0v4.0.1

This results in bloated/incorrect release notes that include all changes between v2 and v4, rather than just the incremental changes since the last v4.x release.

Root Cause

In src/index.js lines 419-435:

const releases = await octokit.rest.repos.listReleases({
  owner: context.repo.owner,
  repo: context.repo.repo,
  per_page: 100
});

if (releases.data.length > 0) {
  // Find the most recent release with a semver tag (vX.Y.Z pattern)
  const semverRelease = releases.data.find(release => {
    const tagName = release.tag_name;
    return tagName && SEMVER_TAG_PATTERN.test(tagName);
  });

  if (semverRelease) {
    previousTag = semverRelease.tag_name;
  }
}

listReleases returns releases sorted by created_at descending. .find() returns the first match — which is the most recently created release, not the semantically highest version. So a hotpatch like v2.0.6 (created after v4.0.0) gets picked as the baseline instead of v4.0.0.

Suggested Fix Approaches

Option A: Use GitHub's "latest" release designation (recommended)

GitHub's API provides a dedicated endpoint to get the release marked as "latest":

const { data: latestRelease } = await octokit.rest.repos.getLatestRelease({
  owner: context.repo.owner,
  repo: context.repo.repo
});
previousTag = latestRelease.tag_name;

Pros:

  • Simple, single API call
  • Respects the user's explicit "latest" designation on GitHub
  • Works correctly even with complex branching/hotpatch strategies
  • No need to implement semver sorting logic

Cons:

  • Relies on the "latest" flag being set correctly (GitHub does this automatically for non-prerelease, non-draft releases created on the default branch — but hotpatches on non-default branches won't override it, which is exactly the behavior we want)

Option B: Semver sort to find the highest version

const semverReleases = releases.data
  .filter(r => r.tag_name && SEMVER_TAG_PATTERN.test(r.tag_name))
  .sort((a, b) => semver.rcompare(a.tag_name, b.tag_name));

if (semverReleases.length > 0) {
  previousTag = semverReleases[0].tag_name;
}

Pros:

  • Doesn't depend on GitHub's "latest" flag
  • Uses semver (already a dependency in this project)

Cons:

  • More code, need to handle edge cases
  • Doesn't account for pre-releases or draft releases
  • listReleases is paginated (100 per page) — would need octokit.paginate() if the repo has >100 releases

Recommendation

Option A is simpler and more robust. It naturally handles the hotpatch scenario because GitHub only updates the "latest" flag for releases on the default branch. Wrap the getLatestRelease call in a try/catch and fall back to the current behavior (or Option B) if it fails (e.g., no releases exist yet).

Additional Context

  • The semver package is already a dependency, so Option B is viable if Option A is rejected
  • The current code already has the SEMVER_TAG_PATTERN regex and semver import
  • This bug only manifests when publishing hotpatches to older version lines — a relatively uncommon but valid workflow
  • Consider also handling the case where listReleases returns more than 100 releases (pagination) if keeping any list-based approach

Metadata

Metadata

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions