Skip to content

Commit b808ce8

Browse files
committed
ci(release): drop the duplicate test run and stop concurrency discarding release runs
`changeset-release.yml` did two things it should not. It re-ran `pnpm test` against a commit already on `main`. That could never keep anything out — only stop the release afterwards, which it did on 2 of the last 27 release-PR merges (runs #3606 and #3901 failed there and skipped the changesets step). It was 31m19s of a 33m22s job; everything else combined is ~90s. The push lane in `ci.yml` tests the same commit across four coverage shards and enforces the thresholds on the merged report, so this copy was duplication ~34 minutes later. Its concurrency group was shared by every push to `main`, which does not queue: GitHub holds one pending run per group and cancels the rest. Over runs #3712-#3911, 93 of 200 were `cancelled` with an empty jobs array — no step ever ran — on the workflow whose last step publishes to npm. The group is now keyed by commit, so nothing is discarded, and ordering moves into a fail-open wait step that holds a run until every older release run has finished. `cancel-in-progress: true` is deliberately not the fix: it keeps one pending slot by killing a run that may be mid-`changeset publish`. Fixes #5404 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
1 parent 15236e0 commit b808ce8

2 files changed

Lines changed: 117 additions & 3 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
---
3+
4+
CI only — this publishes nothing, declared explicitly with an empty frontmatter rather
5+
than left undeclared.
6+
7+
Two changes to `.github/workflows/changeset-release.yml`, the workflow whose last step
8+
publishes to npm.
9+
10+
**The duplicate `pnpm test` is gone.** It re-tested a commit already on `main`, so it
11+
could never keep anything out — it could only stop the release afterwards, and it did:
12+
runs #3606 and #3901, 2 of the last 27 release-PR merges, failed there and skipped the
13+
changesets step entirely. It cost 31m19s of a 33m22s job (medians over the 106 runs of
14+
#3712-#3911 that executed; install 6s, build 35s, the changesets action 24s). The
15+
backstop for `main` is `ci.yml`'s push lane, which runs the whole suite under coverage
16+
across four shards and enforces the thresholds on the merged report.
17+
18+
**The concurrency group is now keyed by commit rather than by branch.** A group shared by
19+
every push to `main` does not queue: GitHub holds one pending run per group and cancels
20+
the rest, so 93 of those same 200 runs were `cancelled` with an empty jobs array — not
21+
one step ever ran. On a workflow that publishes, a discarded run is a discarded publish.
22+
Ordering moves into a fail-open wait step that holds a run until every older release run
23+
has finished, so runs are still serialised, but a contended lane now delays a release
24+
instead of dropping it.

‎.github/workflows/changeset-release.yml‎

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,33 @@ on:
55
branches:
66
- main
77

8-
concurrency: ${{ github.workflow }}-${{ github.ref }}
8+
# One concurrency group per COMMIT — never one shared by every push to `main`.
9+
#
10+
# A shared group does not queue. GitHub keeps at most ONE pending run per group and
11+
# cancels the previous pending run whenever a newer one arrives, so on a branch that
12+
# takes ~18 merges a day a long-running release job discards most of what follows it.
13+
# Measured over runs #3712-#3911 (2026-08-17 .. 2026-08-20, 200 runs): 93 were
14+
# `conclusion: cancelled` with an EMPTY jobs array — not one step ever ran — while the
15+
# 106 that did execute held the group for a median of 33m22s. Median wait for a run
16+
# that survived: 5m16s (objectui#5404, method and figures from objectui#5395).
17+
#
18+
# The last step of this workflow publishes to npm, so a discarded run is a discarded
19+
# publish. `cancel-in-progress: true` is NOT the fix: it keeps one pending slot by
20+
# killing the run that may be mid-`changeset publish`, which is strictly worse than
21+
# any wait.
22+
#
23+
# Keyed by `github.sha` the group holds exactly one run, so nothing is ever discarded.
24+
# Serialisation is not given up — it moves into the "Wait for older release runs" step
25+
# below, which queues runs for real and in order, and which can only ever delay a run,
26+
# never drop one.
27+
concurrency:
28+
group: ${{ github.workflow }}-${{ github.sha }}
29+
cancel-in-progress: false
930

1031
permissions:
32+
# `actions: read` lets the queue step below list this workflow's own runs. Declaring
33+
# any `permissions:` block sets every unlisted scope to `none`, so it has to be named.
34+
actions: read
1135
contents: write
1236
pull-requests: write
1337
id-token: write
@@ -17,6 +41,59 @@ jobs:
1741
name: Changeset Release
1842
runs-on: ubuntu-latest
1943
steps:
44+
# The queue the `concurrency:` key above cannot provide. GitHub offers exactly two
45+
# behaviours for a contended group — cancel the running one, or discard the pending
46+
# ones — and neither is acceptable on a workflow that publishes. So the ordering is
47+
# done here: a run waits until every release run older than itself has finished, then
48+
# proceeds. Ordering is by `run_number`, a total order, so the oldest run is always
49+
# free to proceed and this cannot deadlock.
50+
#
51+
# It is deliberately FAIL-OPEN. Every exit path is `exit 0`, and the step additionally
52+
# carries `continue-on-error: true`: if the API cannot be read, or an older run is
53+
# wedged past the cap, this step gives up waiting and lets the release proceed. A run
54+
# that never runs is the defect this queue exists to remove — degrading to today's
55+
# unserialised behaviour is the acceptable failure, blocking a publish is not.
56+
- name: Wait for older release runs to finish
57+
continue-on-error: true
58+
env:
59+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
60+
RUN_NUMBER: ${{ github.run_number }}
61+
# A release job is ~90s of work; this cap is a wedged-run escape hatch, not a
62+
# normal wait.
63+
MAX_WAIT_SECONDS: '900'
64+
POLL_SECONDS: '20'
65+
run: |
66+
set -uo pipefail
67+
68+
# A rename of this file makes the path 404, `gh` exits non-zero, and the step
69+
# fails open below — it does not silently stop serialising while looking green.
70+
runs_path="repos/${GITHUB_REPOSITORY}/actions/workflows/changeset-release.yml/runs?per_page=100"
71+
older_jq='[.workflow_runs[] | select(.status != "completed" and .run_number < '"${RUN_NUMBER}"')]
72+
| .[] | " waiting on run #\(.run_number) (\(.status)) \(.html_url)"'
73+
74+
deadline=$(( $(date +%s) + MAX_WAIT_SECONDS ))
75+
while : ; do
76+
if ! older=$(gh api "${runs_path}" --jq "${older_jq}"); then
77+
echo "::warning::Could not list this workflow's runs, so the release lane is not serialised for this run. Proceeding — this step may delay a release, never block one (objectui#5404)."
78+
exit 0
79+
fi
80+
81+
count=$(printf '%s\n' "${older}" | grep -c 'waiting on run' || true)
82+
if [ "${count}" -eq 0 ]; then
83+
echo "No older release run is still in flight. Proceeding."
84+
exit 0
85+
fi
86+
87+
if [ "$(date +%s)" -ge "${deadline}" ]; then
88+
echo "::warning::${count} older release run(s) still unfinished after ${MAX_WAIT_SECONDS}s. Proceeding without waiting further — a release run that never executes is the defect this queue exists to prevent (objectui#5404)."
89+
exit 0
90+
fi
91+
92+
echo "${count} older release run(s) still in flight:"
93+
printf '%s\n' "${older}"
94+
sleep "${POLL_SECONDS}"
95+
done
96+
2097
- name: Checkout Repo
2198
uses: actions/checkout@v7
2299
with:
@@ -54,8 +131,21 @@ jobs:
54131
- name: Install dependencies
55132
run: pnpm install --frozen-lockfile
56133

57-
- name: Run tests
58-
run: pnpm test
134+
# ⛔ Do not add a `pnpm test` step back here.
135+
#
136+
# There used to be one, and it was 31m19s of a 33m22s job (medians over the 106 runs
137+
# of #3712-#3911 that executed; everything else combined is ~90s — install 6s, build
138+
# 35s, the changesets action 24s). It re-tested a commit that is ALREADY on `main`, so
139+
# it could not keep anything out; the only thing it could do was stop the release
140+
# afterwards, and it did: 2 of the last 27 release-PR merges (runs #3606 and #3901)
141+
# failed here and skipped the changesets step entirely. #3606 was the run that would
142+
# have published @object-ui/*@17.5.0, which reached npm 6h50m late, on a later push
143+
# that happened to still find `.changeset/` empty.
144+
#
145+
# The backstop for `main` is `ci.yml`'s push lane, which runs the whole suite under
146+
# coverage across four shards on every push and enforces the thresholds on the merged
147+
# report (objectui#5403). That is the copy that tests this commit; this one only ever
148+
# duplicated it, ~34 minutes later.
59149

60150
- name: Build packages
61151
run: pnpm build

0 commit comments

Comments
 (0)