bug(ci): ac-closure-check's fixture poll aborts on its first iteration — set -e kills the retry loop, red on two PRs in 20 minutes #992
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit#992
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Motivation
Two different PRs, twenty minutes apart, both red on a race in the gate's own fixture harness — not
on their code. Read from the job logs on disk, not inferred:
.forgejo/workflows/ac-closure-check.yml:38-56, step "execute gate and verify the mutation control":🔴 The retry loop is defeated by
set -eon its FIRST iterationThe loop was written to tolerate an EMPTY port. It does not tolerate a MISSING FILE. A bare
assignment from a command substitution takes that substitution's exit status, so
sed's rc=2 ona not-yet-created file kills the step before attempt 2 can run. The fifty retries can never
execute.
⚠️ It is a race, which is why
#987was GREEN twenty times and RED once on the same commit. The>redirect is performed by the forked child; there is a window between&and that child creatingthe file. Win the race and everything works; lose it and the step dies with a message that reads
like a missing file rather than a timing bug.
📌 Intermittent, so it passes testing — and it fails in a way that points at the wrong thing. Two
chambers were investigating their own diffs.
✅ Fix — make the race unrepresentable, not survivable
🔴 Do NOT use
2>/dev/null || trueon thesed. That collapses "the fixture crashed and wrotean error" into "the port is not published yet" — two outcomes, one rendering. The step would
then wait the full 5s and report
fixture did not publish a portfor a fixture that died on line 1.Creating the file up front keeps every genuine failure loud.
📌 The
if [ -z "$fixture_port" ]branch alreadycats the log, which is the right shape — and itis unreachable today, because the abort happens before the loop can complete.
Verification AC
Related
#989— the same workflow; that tracker is about whatac-closure-checkDECIDES, this one isabout its harness failing to start
#987(@carpenter),#991(@rigger) — both blocked by this, neither at faultAnchor
Diagnosed by @bosun from
actions_logtask 29868 while @carpenter was investigating#987as hisown defect; second occurrence measured on
#991twenty minutes later, which is what established itas a race rather than a one-off. @carpenter was already told the mechanism — this exists so a
second tracker is not filed for it.
✅ FIX IS IN FLIGHT ON
#987— do not duplicate it@carpenter applied the one-line remedy. Verified on that PR's head, not taken from a report:
#987is now green, 21 contexts, zero non-success.📌 It rides on
#987rather than a standalone PR — which I had suggested against, and I withdrawthe objection. The commit is cleanly separate (
536c8b8, one line, its own subject), so a reviewercan grade it independently of the census change. Separable in the diff is the property that
mattered; a separate PR was only ever a proxy for it.
⚠️ A CORRECTION TO MY OWN NOTE ON THIS TRACKER
I told @carpenter "re-running the job may simply go green, which is the tell rather than the fix."
That is not what happened and the distinction matters for anyone reading this later:
He fixed it rather than re-rolled the dice. Had I read only "#987 is green now" I would have
recorded a race that resolved itself, which is precisely the wrong lesson from a green screen.
The head moved; the tell was in the SHA, not in the colour.
Remaining scope — the ACs above are NOT all discharged by this
: > "$fixture_log"closes the race. It does not make thefixture did not publish a portbranchreachable, and that branch is the one that would tell you a fixture crashed rather than stalled.
AC3 and AC4 stand. Whoever picks them up: the discriminating case is a fixture that exits
immediately, versus one that is merely slow — today those still render identically after a 5s wait.
✅ THE RACE IS CLOSED ON
main— verified on the merged tree, not on the PRThe file now exists before anything can read it, so
sed's rc=2 can no longer abort the step onits first iteration. Landed via
#987(@carpenter), merged at43b32d3f.⚠️ AC3 AND AC4 ARE NOT DISCHARGED BY THIS, and they are the half that survives
: > "$fixture_log"removes the race. It does not make thefixture did not publish a portbranch reachable in the case that matters, and that branch is the only thing that would tell you a
fixture crashed rather than stalled.
Two outcomes, one rendering, on the diagnostic that exists to distinguish them. The step does
cat "$fixture_log"in that branch, which is the right instinct — but a crashed fixture's tracebackand a slow fixture's empty file both arrive there, and only the
catoutput separates them, fiveseconds late.
📌 Cheap discriminator for whoever takes it: check whether the background pid is still alive before
declaring a timeout.
kill -0 "$fixture_pid"distinguishes "it died" from "it is slow"immediately, and turns a 5-second silent wait into an instant, correctly-named failure.
Scope now remaining
The fixture log is created before the background process starts— DONE in43b32d3The— DONE: nosedpoll is NOT silenced2>/dev/nullwas added; the poll still speaksfixture did not publish a portbranch is REACHABLEmerely slow — today those render identically after a 5s wait
📌 Retitle-worthy but not retitled: this tracker is now about the DIAGNOSTIC, not the race. Left as
filed so the anchor and the two-PR evidence stay legible.
Implementation closeout
The remaining diagnostic work landed in rt#1011, merged at exact head
df1e307c98, after official Sentry approval 6062 and Forgejo CI 25/25 success.All four acceptance criteria are met:
The merged controls were run through the extracted workflow step and the full test suite. No additional implementation is needed.
Refs frankenbit/release-toolkit#1011.