fix: Report a release that released nothing as skipped #82
No reviewers
Labels
No labels
priority/P0
priority/P1
priority/P2
priority/P3
size/L
size/M
size/S
size/XL
size/XS
state/blocked
state/done
state/in-progress
state/in-review
state/needs-refinement
state/ready
type/bug
type/chore
type/feature
type/refactor
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Blocks
You do not have permission to read 13 dependencies
Reference
amtronics/platform-actions!82
Loading…
Reference in a new issue
No description provided.
Delete branch "feature/81-no-release-status"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Issue
Closes #81
Problem
A merge that warrants no release still notifies, and claims an outcome that did not happen.
ahmad/tripannounced its last such run as ❌ failure; the repositories converted by #72 announce it as ✅ success. Nothing succeeded and nothing failed — nothing ran, and the reader loses exactly the distinction they open the message for.Solution
notify-telegramgains an icon forskipped, a status it already documented but had no branch for, so it was landing on the catch-all warning. The shared template now sendsskippedwhenreleasedis nottrue, checked before the job results — on a no-release run publish and deploy are skipped, which would otherwise read as a healthy release.Review notes
Four behaviour cases cover the icon table, including that
skippedis distinguishable from both success and failure, and that an unknown status is still announced with a warning rather than swallowed. The catch-all stays a warning deliberately: an unknown status is a bug in the caller and should look like one.Consumers copy this template, so each repository needs its own follow-up;
ahmad/tripandahmad/portfolioare the urgent two, because they report failure today.Risks and trade-offs
A third state means a reader who filtered on ✅/❌ sees something new. That is the point, but it is a visible change to every release message.
WIP: fix: Report a release that released nothing as skippedto fix: Report a release that released nothing as skippedOne finding, inline, non-blocking — the fourth outcome, left where the third one was. The change itself is right and I checked the part the suite cannot reach.
Verified
The icon table is properly pinned. Mutated three ways at
64ba9ff3:Asserting
calls_not_contain=["✅","❌"]alongsidecalls_contain=["➖"]is what makes the first two both fail — an icon test that only asserted the new icon would have passed the second mutation.check-actionsgreen, whole suite green.The template expression is correct, and it is the part no behaviour case can exercise, so I traced it against the expression semantics rather than assuming.
(A && B && C) && (D && 'success' || 'skipped') || 'failure': withA&&B&&Ctrue the outer&&yields the inner value, which is'success'whenreleased == 'true'and'skipped'otherwise; with it false the outer&&yieldsfalseand the||yields'failure'. The hazard in this idiom is a middle operand that can be falsy — an empty string there collapses the whole expression to the last branch — and both inner values are non-empty literals, so it does not bite. Checkingreleasedbefore the job results is also the right order, exactly as the comment says: on a no-release run publish and deploy areskipped, which the first three conjuncts accept.needs.version.outputs.releasedis only ever the literaltrueorfalsewhenversionsucceeds (release/action.ymlwrites one or the other and exits 0), so there is no third value to fall through.The README line and the input description now both list four statuses, and the catch-all staying a warning is the right call for the stated reason.
The scope note is accurate and worth repeating: consumers hold their own copy of this template, so merging this changes nothing for them until each is converted. The action half is shared via
@v3but inert until a consumer sendsskipped.CI green (
validate).@ -315,0 +320,4 @@${{ (needs.version.result == 'success'&& (needs.publish.result == 'success' || needs.publish.result == 'skipped')&& (needs.deploy.result == 'success' || needs.deploy.result == 'skipped'))&& (needs.version.outputs.released == 'true' && 'success' || 'skipped')The fourth outcome is still collapsed into
failure, and it is the same shape as the bug this fixes.needs.<job>.resulthas four values. This expression now distinguishes three — and acancelledjob falls into the|| 'failure'branch, so a cancelled release announces ❌ whilenotify-telegramcarries a ⚪ icon for exactly that case and the README documents it as one of the four statuses. Nothing in the template ever sendscancelled.It is reachable, and
if: always()on this job is what makes it so: the notify job runs when the upstream ones are cancelled, and reports failure.concurrency.cancel-in-progress: falserules out the automatic path, so what remains is a human cancelling a run — uncommon, which is why this is not blocking, but it is precisely the reader whose message is now wrong in the same way#81describes: nothing failed.The shape you have already written extends to it directly:
Cancelled first, because a cancelled job is neither a success nor a failure and the later conjuncts would read it as the latter — the same reason
releasedis checked before the job results here.Worth deciding rather than deferring, because of the follow-up cost you name yourself: every consumer copies this template, so the difference between three outcomes and four is the difference between converting each repository once and converting it twice. If you would rather leave
cancelledreading as a failure, that is defensible — a cancelled release usually is something a person wants to look at — but then the action's ⚪ branch has no caller, and the README's four-status line has no producer, which is worth a sentence somewhere so the next reader does not go looking for the path that sends it.64ba9ff3f22a39b7bc61Approved. The fourth outcome is in, and the ordering question I flagged is resolved the way it had to be.
The icon is pinned. Mutated at
02c21d3e:calls_not_contain=["❌", "⚠"]besidecalls_contain=["⚪"]is what makes both fail — the same shape that made theskippedcase worth having, and it is why a test asserting only the new icon would have passed the second mutation.The expression is correct, and this time I evaluated it rather than traced it. No behaviour case can reach a workflow expression, so I implemented the
&&/||value semantics and enumerated all 192 combinations of(version, publish, deploy) × released:The last row matters: an empty
released— which is what a reader would fear from a job that wrote no output — lands onskipped, not on a falsesuccess. And the nesting is safe for the same reason as before: every operand that can be returned is a non-empty literal, so nothing collapses to the last branch.One consequence worth stating rather than discovering.
cancelledis checked first, so a run where something failed and something else was cancelled reportscancelled. That is the right way round — checking failure first is exactly what produced#81, since a pure cancellation leavesversion.result != 'success'and would report ❌ — but it does mean the icon answers "was this run stopped" before "did anything break". A reader is already opening a cancelled run, and the message carries the link, so I would leave it; it is worth knowing it is a choice and not an accident.Doing both outcomes in one PR was the right call, and it settles what I raised last time: consumers copy this template, so three outcomes then four would have meant converting eleven repositories twice.
02c21d3e41f83ef17decNew commits pushed, approval review dismissed automatically according to repository settings
Approved, re-affirmed after the rebase over
#80.Two of this PR's four files differ from the head I approved —
behaviour-tests.pyandREADME.md— and both are the merge rather than new work: the script now carries#80's excluded-types cases and this PR's cancelled case in one file, and the README carries both status-row edits. That is the file where two PRs adding cases to the same list can drop one, so I checked it by running rather than reading: whole suite green,check-actionsgreen, and the cancelled icon is still pinned — deletingcancelled) ICON="⚪"fails exactly a cancelled release is neither failed nor merely unknown.The verdict is otherwise unchanged from
02c21d3e, where the work itself was verified:calls_not_contain=["❌", "⚠"]besidecalls_contain=["⚪"]being what makes a mapping mutation fail rather than pass.(version, publish, deploy) × releasedgiving success 4, skipped 8, cancelled 111, failure 69, with an emptyreleasedlanding onskippedrather than a falsesuccess.cancelledfirst is the right precedence, since checking failure first is what produced#81.