fix(linear-release-sync): classify release stability by github prerelease flag#156
Open
Piotr1215 wants to merge 3 commits into
Open
fix(linear-release-sync): classify release stability by github prerelease flag#156Piotr1215 wants to merge 3 commits into
Piotr1215 wants to merge 3 commits into
Conversation
…ease flag Loft Bot double-commented "Now available in stable release vX.Y.Z" on already-released Linear issues (DEVOPS-1006, e.g. ENG-8307 on the vCluster 0.28 line). The trigger was vCluster backport patch tags like v0.28.2-patch.1: they are published with prerelease=false on GitHub but read as semver-prereleases because of the -patch.N suffix. The action decided "is this a shippable stable release?" by parsing the tag string (isStableRelease via Masterminds Prerelease()), which classified -patch.N as a prerelease and skipped it. The old inline hack/linear-sync still running on release branches used a substring blocklist that did the opposite and treated -patch.N as stable with no dedup. Neither matched intent. GitHub already records the author's intent on the release object, and it is correct for every vCluster tag type: stable and -patch.N are prerelease=false, -rc/-alpha are prerelease=true. Drive the decision from that flag instead of the tag string. This makes the action correct for the patch release type so release branches can migrate to it, where its tag-scoped dedup prevents the cross-repo duplicate (vcluster and vcluster-pro cut the same tags). Removes the isStableRelease tag-string classifier, adds IsPrerelease to the fetched release, and passes isStable into MoveIssueToState. References DEVOPS-1006
loft-bot
reviewed
Jun 19, 2026
loft-bot review on #156 flagged that the prerelease-flag tests asserted tautologies: they computed isStable := !isPrerelease and checked it against wantStable, which every row set to !isPrerelease, so the identity (!x == !x) could never fail. If the production decision were ever inverted, the DEVOPS-1006 regression they document would still pass, guarding nothing. extract the two decisions into named functions the tests drive directly: - releaseIsStable(release) in main.go: the !IsPrerelease classification. - decideReleaseAction(...) in linear.go: the skip / move-to-released / stable-comment branch selection MoveIssueToState makes before any Linear API call. MoveIssueToState now switches on it; behavior is unchanged. TestReleaseIsStable and TestDecideReleaseAction now call the real functions, so an inversion fails (verified by mutation: flipping either decision turns the -patch.N row red). also drop the dead MockLinearClient.MoveIssueToState (stale signature, never called) and move the DEVOPS-1006 RCA out of the versioned action dir into the PR description, where a one-time ticket investigation belongs. References DEVOPS-1006
… github prerelease flag DEVOPS-1006 follow-up. The prior fix on this branch classified a release as shippable from GitHub's prerelease flag on the release object. Denise's call (2026-06-19): go by the tag name instead. The flag carries more manual human error on which release type to pick, and vCluster usually publishes a release as a prerelease first and promotes it to stable later, so the flag is a moving target the sync can read at the wrong moment (the workflow fires on release:created, before promotion). Classify from the tag: stable when there is no semver prerelease component or it is the backport patch marker (patch / patch.N), so v0.28.2-patch.1 syncs as a real release; -rc/-alpha/-beta/-dev/-pre/-next are prereleases and are skipped. Using an allowlist (no-suffix or patch) rather than a prerelease blocklist means an unknown future suffix defaults to prerelease, the safe direction. Avoids the Masterminds Prerelease()=="" trap that skipped -patch.N. Drop the IsPrerelease field added to the fetched release. Tag-scoped dedup is unchanged; it remains the fix for the cross-repo double comment once release branches migrate to this action. References DEVOPS-1006
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Loft Bot double-commented "Now available in stable release vX.Y.Z" on already-released Linear issues (DEVOPS-1006, e.g. ENG-8307 on the vCluster 0.28 line).
The action decided "is this a shippable stable release?" by parsing the tag string (
isStableReleasevia MastermindsPrerelease()). vCluster's backport patch tags likev0.28.2-patch.1are published withprerelease=falseon GitHub but read as semver-prereleases because of the-patch.Nsuffix, so the action misclassified them. (The old inlinehack/linear-syncstill running on release branches did the opposite via a substring blocklist and had no dedup, which is what actually produced the live duplicates.)This PR switches the classification to the signal GitHub already records correctly for every vCluster tag type:
prereleasev0.34.4(stable)falsev0.28.2-patch.1(patch)falsev0.35.0-rc.9,-alpha.8trueKey Changes
releases.go: addIsPrereleaseto the fetchedRelease(GraphQLisPrerelease).main.go: classify withreleaseIsStable(currentRelease)(!IsPrerelease) and pass the result down; log the decision.linear.go:MoveIssueToStatetakesisStableas a parameter and routes throughdecideReleaseAction(skip / move-to-released / stable-comment); removed theisStableReleasetag-string classifier.TestReleaseIsStableandTestDecideReleaseActiondrive the real decision functions, so an inverted classification fails (confirmed by mutation testing). AddedIsPrereleaseparsing assertion inFetchReleaseByTag. Removed deadMockLinearClient.MoveIssueToState.Root cause
ENG-8307 received two identical "Now available in stable release" comments for the same tag:
v0.28.2-patch.1v0.30.4Both pairs are the same tag, so this is not the DEVOPS-874 wrong-previous-tag case. The "double" is two repositories (vcluster and vcluster-pro) releasing the same version tag, each running its own Linear sync against the shared Linear issue: one comment per repo.
The release-branch jobs do not use the pinned action binary. A
release-triggered workflow runs from the tag's ref, so a patch/backport tag on an old branch runs that branch's oldrelease.yamland inlinehack/linear-sync, never the migrated action onmain. That old code has three defects:isStableReleaseis a substring blocklist (-alpha,-beta,-rc,-dev,-pre,-next) that omits-patch, sov0.28.2-patch.1classifies as stable and fires the "now available in stable" comment on already-released issues. This is Denise's "None"/patch release-type intuition.linear.goposts unconditionally, so a second repo releasing the same tag always re-comments. Dedup was only ever added to the extracted action.v0.28.2-patch.1, predecessor resolved tov0.27.3, a 72-issue compare range that re-touches everything already shipped in 0.28.x.The deeper issue: both the old and new classifiers re-derive "is this a release?" from the tag string and both get
-patch.Nwrong in opposite directions (substring blocklist over-fires, MastermindsPrerelease()skips). GitHub already records the author's intent on the release object and it is correct for every tag type vCluster ships, which is what this PR switches to.Dependencies / follow-ups
This is step 1 of the fix (action-level correctness). It does not fully resolve DEVOPS-1006 on its own:
linear-release-sync/v1release binary to include this change (force-pushing the tag does not rebuild; useworkflow_dispatchofrelease-linear-release-sync.yaml).sync_linearjob to this shared action on active vcluster + vcluster-pro release branches (deletes the no-dedup / substring-blocklist inline code; tag-scoped dedup then kills the cross-repo duplicate).previous-tagexplicitly for patches) so already-released issues are not re-announced.-patch.Nscheme, or use real patch versions / build metadata to avoid the semver-prerelease trap.Testing
make test-linear-release-sync(all green),go build,go vet,gofmtclean on changed files. Mutation-checked: invertingreleaseIsStableor theisStablebranch indecideReleaseActionturns the-patch.Nregression rows red.References DEVOPS-1006