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:
v4.0.0 is the latest release (marked LATEST on the GitHub Releases page)
- A hotpatch
v2.0.6 is published to the v2 branch (for users still on v2)
- When
v4.0.1 is later published, the action generates release notes comparing v2.0.6 → v4.0.1 instead of the correct v4.0.0 → v4.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
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:
v4.0.0is the latest release (markedLATESTon the GitHub Releases page)v2.0.6is published to the v2 branch (for users still on v2)v4.0.1is later published, the action generates release notes comparingv2.0.6→v4.0.1instead of the correctv4.0.0→v4.0.1This 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.jslines 419-435:listReleasesreturns releases sorted bycreated_atdescending..find()returns the first match — which is the most recently created release, not the semantically highest version. So a hotpatch likev2.0.6(created afterv4.0.0) gets picked as the baseline instead ofv4.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":
Pros:
Cons:
Option B: Semver sort to find the highest version
Pros:
semver(already a dependency in this project)Cons:
listReleasesis paginated (100 per page) — would needoctokit.paginate()if the repo has >100 releasesRecommendation
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
getLatestReleasecall 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
semverpackage is already a dependency, so Option B is viable if Option A is rejectedSEMVER_TAG_PATTERNregex and semver importlistReleasesreturns more than 100 releases (pagination) if keeping any list-based approach