Uh oh!
There was an error while loading. Please reload this page.
ci(shadcn): close the three declared alarm-channel gaps — timeout, analyze crash, consecutive unreachability - #4174
Merged
Conversation
PR #3497 built the alarm channel and disclosed three gaps it left open. This closes all three, and moves the classification out of YAML so the new branching is testable: - gap 2: `timeout-minutes: 20` on the job. The online step is 46 serial registry requests and `fetchUrl` sets no socket timeout, so a hang was bounded only by GitHub's 360-minute default. Sized from measurement: 49s longest of 30 observed runs, ~13min compound degraded worst case. - gap 3: the `analyze` step's exit code is captured instead of hidden behind `continue-on-error`, and an analyze crash enters the SAME issue channel as check failures. Its output is now captured with `2>&1`, so the crash the alarm reports is actually in the alarm's body. - gap 1: N consecutive unreachable runs escalate into that same channel (N=3). Cross-run state is carried by two marker steps whose names and conclusions the next run reads via the Actions API — the cheapest honest mechanism of the five costed in `readRegistryStreak`'s header. The single-run tolerance ruling is untouched for runs 1..N-1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
The latest updates on your projects. Learn more about Vercel for GitHub. |
This was referenced Aug 10, 2026
yinlianghui
marked this pull request as ready for review
August 10, 2026 16:33
Uh oh!
There was an error while loading. Please reload this page.
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 freeto 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.
Fixes#3586
Closes all three gaps PR #3497 declared and deliberately left open, per the PM claim comment's per-gap direction.
Premise check (against
origin/main@0e67b53ff)All three still live, re-verified in the tree rather than taken from the card:
grep 'timeout-minutes'over.github/workflows/shadcn-check.yml: no match, on the job or any step.Analyze components (offline)step still carriedcontinue-on-error: true, and the classification block only ever read thecheckstep's exit code.One premise detail worth recording because it changes how gap ① reads: the registry is currently reachable from CI. Run
31374857502(2026-08-10) logsRegistry: 0 cached, 46 fetchedwithErrors: 0. So the escalation being added is a real backstop, not a workaround for a standing outage.Gap ② —
timeout-minutes: 20The online step is 46 serial registry requests, and
fetchUrlinscripts/shadcn-sync.jssets no socket timeout, so a black-holed connection was bounded by nothing but the job's default of 360 minutes.Arithmetic, from measurement (stated on the job in the workflow too):
31374857502pnpm install --frozen-lockfileDegraded-but-alive worst case at 10 s per request is 46 x 10 s = 7.7 min, plus ~35 s overhead; budgeting ~5 min for a cold store on a lockfile change gives ~13 min of compound worst case. 20 is ~1.5x that, ~24x the longest run ever observed, and turns a hang from six hours into twenty minutes.
Gap ③ — an analyze crash enters the same issue channel
component-analysis.jshas exactly one non-zero exit: an uncaught crash. #3497 left the step tolerant because there was "no verdict here to swallow" — true of the drift verdict, false of the step itself, so a crash turned the job red on an unwatched weekly schedule and reached nobody.continue-on-erroris gone; the exit code is captured explicitly the way ci(shadcn): 按失败类别把 shadcn:check 的退出码路由进 issue 通道 #3497 captured the check step's, and the step still exits 0 by construction.analyze: ok | broken, andbrokenroutes into the same issue — same labels, same de-duplication, no second channel and no newcontinue-on-error.2>&1 | tee. The old> analysis.txtsent only stdout to the file the issue body quotes, and a crash writes its stack to stderr — so the alarm would have reported a crash whose error text it had thrown away.Gap ① — N consecutive unreachable runs escalate
Cost shape of every candidate mechanism (full version in
readRegistryStreak's header):conclusionsuccess— identical to a clean run. Cannot express the distinction at any price.actions: read, state retained with the run (90 days ≈ 13 weekly runs)::warning::prose re-parsed a week laterlabel:shadcn-synchas matched 0 issues, ever), so it needs either opening the issue on run 1 — which is the alarm, contradicting the ruling this card leaves untouched — or a private state issue, i.e. the second channel the card forbids.actions/cacheImplementation: two no-op marker steps whose names and conclusions are the state the next run reads back. Three-valued by construction — exactly one succeeds in a run that reached a verdict, and neither appears in a run that died earlier, so "we don't know" stays distinguishable from "reachable" and only
unreachableextends the streak. The walk stops at the first non-unreachable run, so it never costs more than N-1 job reads.N = 3. Weekly cron, so N is weeks of blindness: N=2 (14 days) fires on any fortnight-long CDN or egress hiccup, which is the noise the single-run tolerance was ruled to avoid; N=4 (28 days) is a month of an early-warning system warning about nothing. 3 is past transient and inside every retention window the mechanism uses.
The single-run ruling is untouched for runs 1..N-1: exit 0,
::warning::, summary line, no issue. The only change to that path is that the warning now says which run of the three it is.Two honest boundaries:
unknownand the streak starts at 1 even if the registry has been unreachable for weeks. Conservative by design.Why the classification moved out of YAML
#3497 verified its logic by hand-running five fixtures once and writing the table into its PR body; nothing re-runs that between weekly cron fires. This card adds two more classified inputs to the same decision, so the classification now lives in
scripts/shadcn-check-report.mjs, covered byscripts/__tests__/shadcn-check-report.test.ts— the same splitscripts/render-budget-comment.mjsuses forperformance-budget.yml.The classifier exits 0 for every classified outcome, alarms included: the alarm is the issue, not the job colour. Its own crash still only turns the job red — the one failure this mechanism cannot route into its own channel, and the reason the logic was moved somewhere it can be unit tested.
Verification
vitest run --project unit scripts/__tests__— 31 files, 606 tests, all passing; the new file is 28 of them.pnpm type-check:scriptsexit 0 ·node scripts/check-control-bytes.mjsOK (3852 files) ·npx eslinton both new files exit 0 ·yaml.safe_loadparses the workflow, and the parsed step list confirmscontinue-on-erroris now absent from every step.node scripts/check-changeset-presence.mjs: "No source of a released package changed in this range, so no changeset is owed" — the presence script arbitrates, as the card directs. Noskip-changesetlabel (decorative in this repo, #3724).Reverse verification — direction predicted before each run
classifyAnalyzeback to alwaysok+continue-on-errorrestoredtimeout-minuteslineThe gap ③ run is the load-bearing one, and the discriminating detail is what did not move: the test named "REVERSE: the identical run with the crash classified ok produces no alarm at all" stayed green through that mutation. It pins the pre-#3586 state — crash, job red on a weekly job, nothing in the triage queue — so if it had gone red with the others, the pair would have been keying off one switch instead of separating the two states.
The gap ① mutation is the one worth the pin: renaming a marker breaks nothing visible, resets the streak forever, and the escalation simply never fires again. That is why the names are asserted against the module's exported constants rather than re-spelled in the test.
Not in scope, per the card
Synthetically firing the token-creates-issue path (#3497 deliberately left it to the first real event) and the
workflow_dispatchpath (already verified 2026-08-07).Generated by Claude Code