Repository navigation
ci(publish-smoke): a cancelled smoke reports pending "nothing measured", never failure - #24503
Conversation
…d", never failure The verdict step mapped every non-success job.status to `failure` with "Release candidate fails a fresh install". job.status is success, failure or cancelled, and a cancelled smoke (superseded by cancel-in-progress when a newer Release run starts, a job timeout, or a manual stop) never reached a verdict, so that red was false and it polluted the status history the header tells a reader to walk. Map cancelled to `pending` with a description saying the run was cancelled before a verdict and measured nothing. success and failure are unchanged, and any other value still falls through to failure. Claude-Session: https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp Co-authored-by: Claude <noreply@anthropic.com>
维护者速读改了什么: 为什么改:你说的「24477 ci 崩了」就是这个原因。每合入一次 main,就会触发一次新的 Release,新的 smoke 会把还在跑的旧 smoke 取消掉;旧运行却往 Version Packages PR 上写一条「发版候选全新安装失败」的红。开发统计了 10-16 以来的 299 次 smoke:224 次成功,75 次被取消,0 次真失败。也就是说,这些红全是假的。今天 08:22Z 和 08:24Z 那两条红都属于这种情况,08:32Z 下一次 smoke 写回了绿。 风险与代价(含回滚):真失败照旧报红, 席位意见:建议批准。改动点正好是产生假红的那一步, 你要做的:给本 PR 一次批准(APPROVED review),之后由 devx 席位落地。 席位核对ACCEPT ·
Generated by Claude Code |
Fixes #24499
Clause-②: no
What changed
One file,
.github/workflows/publish-smoke.yml(+14 / -5, 19 changed lines):Report verdict to the release PR headstep now mapsjob.statuswith acase.cancelledwritespendingwith the description "Cancelled before a verdict (superseded by a newer smoke, timed out, or stopped by hand) — nothing measured" (106 characters; the status API caps descriptions at 140).successandfailurewrite exactly what they wrote before. Any other value still falls through tofailure.pendingreading "Cancelled before a verdict" closes a run that measured nothing.The step's
if:(always() && needs.resolve.outputs.report-sha != ''), theMark pendingstep,resolve-guard, the concurrency block and every other job are untouched.Mapping, before and after
job.statusin the verdict stepsuccesssuccess· Fresh install of the release candidate: auth + CRUD greenfailurefailure· Release candidate fails a fresh install — see the run logcancelledtimeout-minutesoverrun (none measured), or a manual stopfailure· Release candidate fails a fresh install — see the run logpending· Cancelled before a verdict (superseded by a newer smoke, timed out, or stopped by hand) — nothing measuredskippedskippedis not ajob.statusvalue, and a skippedpack-smokejob runs no steps, so nothing is written before or after this changefailurefailure(the*)arm keeps the fail-closed default)How I checked the table: I extracted the step's
run:body from the YAML, substituted eachjob.statusvalue, and ran it under bash with a stubghthat prints thestate=anddescription=it receives. I did this atBASE959ee31 and at head 0c5ad58. The table above is that output. The probe script lives in my scratchpad; no test file is added.Mechanism, measured (each PM hypothesis)
origin/main959ee31. Confirmed as the dispatch described it. The verdict step is at:236–:249:if: always() && needs.resolve.outputs.report-sha != '', and anyjob.statusother thansuccessbecamestate=failurewith "Release candidate fails a fresh install — see the run log". The concurrency block is at:73–:75:group: publish-smoke-${{ github.event_name }}-${{ github.ref }}withcancel-in-progress: true.github/docsat dae83f7d28,content/actions/reference/workflows-and-actions/contexts.md) say: "job.status… Possible values aresuccess,failure, orcancelled".expressions.mdsaysalways()"Causes the step to always execute … even when canceled".workflow-syntax.mdsaysjobs.JOB_ID.timeout-minutesis "The maximum number of minutes to let a job run before GitHub automatically cancels it", so a job timeout is also acancelled.Buildiscancelled,Publish smoke (packed tarballs)isskipped, andReport verdict to the release PR headran and concludedsuccess. The job iscancelled.4ed16392eeshows both runs writingfailure(08:22:52Z and 08:24:32Z). So thealways()step does run on a cancel, andjob.statusthere is notsuccess. No step failed, so of the three documented values it readcancelled.Resolve targetjob of 39222297525 started at 08:22:56Z. Run 39222297525 ended at 08:24:36Z andResolve targetof 39222454134 started at 08:24:37Z. So a cancelled run's write always lands before the newer run's ownpending.pending. The four-axis reasoning is in the next section.check:workflow-status-functionsscopes job-levelif:only, and this diff changes noif:.Route choice — (b)
pending+ "nothing measured"; not (a) silence, noterrorCensus. I listed the
publish-smoke.ymlruns through the REST run list (last 300, read 2026-10-19 around 08:40Z). Of these, 299 areworkflow_runsmokes created between 2026-10-16T00:25Z and 2026-10-19T08:23Z: 224success, 75cancelled, 0failure. For every one of the 75 cancelled runs, a newer smoke was created before it ended. The longest cancelled run lasted 9.1 minutes against a 45-minute job timeout. So no timeout and no stand-alone cancel shows up in the window.The options and their failure modes.
pendingfollows. But the cancelled run's last entry stays "Installing the release candidate into a fresh project…", which stops being true the moment the run is cancelled. Someone walking the history then has to infer "superseded" from twopendingentries in a row. On a timeout or a manual stop with no later smoke, the head would show a false "Installing…" indefinitely.error+ "nothing measured". Never leaves a stalepending. However,errorrenders red, just likefailure, and the combined status rolls it up as a failure. The window between the cancelled run's write and the newer run'spendingis about 15 seconds both before and after this change (08:22:52Z → 08:23:08Z, 08:24:32Z → 08:24:46Z). Soerrorkeeps the exact red flash the maintainer escalated, on about a quarter of all smokes. Only the wording would change.pending+ "nothing measured" (chosen). No red on a supersession. The cancelled run closes its own entry in words, so the history explains itself. Its stale case is bounded:mainstarts a Release run, and that run starts a new smoke on the open release PR.pendingthat says "nothing measured". That is never green, and it never claims a measurement.Four axes.
casewith no new job and no extra API call. I rejected discriminating supersession from a timeout at run time (for example by looking up newer runs during the cancel): it adds machinery and new failure modes, and nothing measured asks for it.failureentries. With the change,failurestays reserved for a smoke that ran and failed (plusresolve-guard's self-describing "never smoked", unchanged). A cancelled run says so in words, and the combined status never reads red for a run that measured nothing. Witherror, a rollup reader would see red again; with silence, a reader has to know an inference rule.Wanted / Done when — conclusions
cancelledmaps topending"Cancelled before a verdict … — nothing measured" and never tofailure.failureexactly as today, andresolve-guard's explicit red stays. Done: thefailurearm is byte-identical in state and description, andresolve-guardis not in the diff.skippedis covered as unreachable).mainafter the merge.workflow_runworkflow runs the file on the default branch, and the dispatch rules out dispatching it. The first reading therefore comes after the merge.pending"Installing the release candidate into a fresh project…";pending"Cancelled before a verdict (superseded by a newer smoke, timed out, or stopped by hand) — nothing measured", linking run A, which concludescancelled;pending"Installing…";successorfailure.failure"fails a fresh install" may come from a run that concludedcancelled. The seat records the first such reading (run id and status history).success(REST run record), so there is no candidate failure to split off and no separate card.Acceptance notes
timeout-minutesis acancelledjob (docs above). It used to read redfailure"fails a fresh install" and now readspending"Cancelled before a verdict … timed out …". No such run appears among the 299 measured. Making a hung smoke read red would be separate work; carrier: none (承接者:无).main), the head keeps the honestpendingabove. It is not red and not green. A re-run of the smoke or the next Release run replaces it.origin/mainmoved afterBASE. It advanced to 7e58e0a (apackages/clitest budget only), which is disjoint from this file, so I did not merge it. The PR's merge ref and the queue cover it.Gates
Derivation:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, run with no paths. It read the change set from git against merge base 959ee31: 1 path, 19 changed lines. It listed 39 commands: 31 matched by path and 8 that run on every change.Local run at head 0c5ad58 (
git rev-parse --short HEAD). Every command's output went to its own log, and the exit code was captured before any pipe. All 39 exited 0:check-aggregator-roster.mjsand--self-testcheck-closing-keyword-parity.mjsand--self-testcheck-comment-mask-corpus.mjscheck-position-name-fold-loaders.mjsand--self-testcheck-self-test-wired.mjsand--self-testcheck-self-test-workflow-commands.mjsand--self-testcheck-step-collectors.mjsand--self-testcheck-whole-set-label-write.mjsand--self-testdocs-audit/check-drift-comment.mjspm/ci-failure.mjs --self-testcheck:agent-test-spelling,check:cross-package-test-inputs,check:declared-population-live,check:driver-memory-censuscheck:gitlink-declared,check:node-version,check:nul-bytes,check:pm-dispatch-gates(fast tier, 1913 cases)check:pm-expected-skips,check:pm-governed-merges,check:pm-post-stamped,check:pm-record-recogniserscheck:pnpm-acquisition,check:pnpm-filter-targets,check:refd-timer-probe,check:required-contextscheck:shard-attestation,check:stall-guard-budget,check:stall-guard-headroom,check:watch-hint-literalcheck:workflow-status-functions,check:workflow-step-name-quotingVerdict lines:
check-workflow-status-functions: OK (scanned 40 workflow file(s), 75 job(s), 32 job-level if: expression(s); 11 read needs.*.outputs.*, all naming a status function).check-workflow-step-name-quoting: OK (scanned 40 workflow file(s) + 2 composite action file(s), 841 step name(s) …)Reconciliation:
--ranwithcommand :: exit Nlines printed✓ dispatch-gates --ran: 39 derived famil(ies) accounted for — 39 run, 0 NOT-MEASURED (a DERIVED zero — all 39 recorded an exit code and none of them is 3).NOT MEASURED, by value: six families take their argv or
env:from a CI run and have no local invocation. They arecheck-required-contexts.mjs --verify-required-setand fivecheck-shard-attestation.mjs --emit/--verifyforms. CI owns them, along with the full farm.No package is touched, so no build closure and no package tests were needed. No changeset: a workflow file publishes nothing, so the PR carries
skip-changeset.维护者速读(草稿)
改了什么:发布冒烟(publish-smoke)被新一轮冒烟取代而取消时,不再往 Version Packages PR 上写红色
failure「候选版本全新安装失败」,改为写黄色pending「在得出结论前被取消 —— 什么都没测」。真正失败的冒烟照旧写failure,成功照旧写success。只改了一个文件,19 行。为什么改:#24477 上那个让人以为「ci 崩了」的红叉,就是被取消的冒烟误报出来的。每次合并到 main 都会触发新一轮 Release 和新一轮冒烟,旧的那轮随即被取消。10/16 以来的 299 次冒烟中有 75 次是这样被取消的,真失败 0 次。
风险与代价(含回滚):
pending(文字如实写明「什么都没测」),不会变红。实测 299 次中超时 0 次。workflow_run只读 main 上的 workflow 文件),要等合并后第一次被取代的冒烟才能看到效果,席位会记录那次读数。席位意见:
你要做的:审阅并批准(
.github/workflows/**属 Tier H,需要你的批准)。合并后无需其他操作。Generated by Claude Code