Repository navigation
fix(release): the stale-prompts sweep retries a transient 5xx, and defers with a warning instead of reading as main red - #28272
Conversation
…efers instead of failing when it stays down The sweep's reads now go through the shared transient-read policy (BOARD_READ_RETRY / readBoard from check-issue-citations.mjs, the one the release gate's full-battery read uses): a 5xx or a named connection failure is retried, 3 attempts per read at 500 / 1000 ms, at most 4 retries a run. A 4xx and any other error stay fatal. A read still failing transiently after that is a ::warning:: plus a job-summary section and exit 0: nothing was judged or cancelled, and the next push re-runs the sweep. The cancel write and the in-flight guard's reads are unchanged. Claude-Session: https://claude.ai/code/session_01QSeKYmTYfaPbLQD4BJfomW Co-Authored-By: Claude <noreply@anthropic.com>
… deferral Claude-Session: https://claude.ai/code/session_01QSeKYmTYfaPbLQD4BJfomW Co-Authored-By: Claude <noreply@anthropic.com>
…he citation gate's closure The transient-read policy (BOARD_READ_RETRY, retryableNetworkCode, boardReadBudget, readBoard, readFailure) moves unchanged out of check-issue-citations.mjs into scripts/board-read.mjs, which imports nothing. check-issue-citations.mjs re-exports every name, so its importers are unchanged; release-pending-publish.mjs imports the leaf, so its ./x.mjs closure no longer drags pm/check-half-states.mjs and friends in -- the closure release-verify-npm's self-test copies into its fixture repo. Claude-Session: https://claude.ai/code/session_01QSeKYmTYfaPbLQD4BJfomW Co-Authored-By: Claude <noreply@anthropic.com>
…port, keeping the census-pinned marker line Claude-Session: https://claude.ai/code/session_01QSeKYmTYfaPbLQD4BJfomW Co-Authored-By: Claude <noreply@anthropic.com>
|
domain:devx#2 · os-justin · session_01QSeKYmTYfaPbLQD4BJfomW 席位核对 — ACCEPT · domain:devx#2 · head
|
domain:devx#2 · os-justin · session_01QSeKYmTYfaPbLQD4BJfomW
Fixes #28165
Clause-②: no
Metadata-protocol: none
维护者速读
scripts/release-pending-publish.mjs sweep)读 GitHub API 遇到 5xx 或连接被重置时,按共享策略重试(每次读 3 次、间隔 500/1000 ms、整次运行最多 4 次重试);重试用尽仍失败时写一条 warning 和 job summary 小节后以 0 退出,不再把 main 的 Release 染红。scripts/下三个文件(未发布,无 changeset):清扫脚本本身;新的叶子模块scripts/board-read.mjs(从check-issue-citations.mjs原样搬出的重试策略,零 import);check-issue-citations.mjs改为 re-export,原有导入方不变。4xx、其它错误、取消请求失败仍然让 job 失败;in-flight 发布守卫的读取语义不变。回滚即 revert 本 PR。scripts/release-pending-publish.mjs的retryingReads/expectOk(重试与分类)和sweep里 catchTransientReadFailure的分支(唯一以 0 退出的地方);scripts/board-read.mjs(确认是原样搬移、零 import)。node scripts/release-pending-publish.mjs --self-test,预期末行146 cases across 35 batteries pass。What changes
scripts/release-pending-publish.mjs, plus the shared retry policy moved into a leaf module (scripts/board-read.mjs) and re-exported fromscripts/check-issue-citations.mjs. No workflow file is touched, so this PR is not Tier H.retryingReads, which wraps the existing HTTP layer inreadBoardunderBOARD_READ_RETRY. That is the same policy and reader the release gate's full-battery read (scripts/ci/scheduled-full-run.mjsreadRuns) uses since the publish-gate twin of this card: 3 attempts per read, 500 / 1000 ms backoff, at most 4 retries for the whole run (at most 3 s of sleep a sweep, against a 10-minute job timeout). Retried: an HTTP 5xx, or a named connection failure (ECONNRESET,UND_ERR_SOCKET; the policy's closed list). Final at once: any 4xx, and any other thrown error.expectOkthrows it as aTransientReadFailure, andsweepcatches only that class. It prints one::warning::line on stderr naming the read, the last answer and the attempts, appends aNOT READ this runsection to$GITHUB_STEP_SUMMARY, and returns 0. Nothing was judged, so nothing was cancelled, and the next push to main re-runs the sweep from scratch. This is the only exit-0 failure path.POSTthat does not answer 202/409. Writes are not retried: they pass throughretryingReadsunchanged.in-flightmode (publishInFlight) still reads through the bare HTTP layer. An unreadable answer isin-flight/unreadableafter one request, which a new case pins.sweepnow takes injectableenv,http,versionCommits,npmStateOf,now,sleep,logandwarn, and returns the exit code (mainexits with it). The live defaults are unchanged.BOARD_READ_RETRY,RETRYABLE_NETWORK_CODES,retryableNetworkCode,boardReadBudget,readBoardandreadFailuremoved unchanged fromcheck-issue-citations.mjsintoscripts/board-read.mjs, which imports nothing. The only addition isexportonsleepMs, which the citation gate's defaults read.check-issue-citations.mjsre-exports every name, so its importers (scheduled-full-run.mjs) are unchanged. Its self-test still drives the policy. The import sits beside the re-export, so the census-pinned marker on line 204 does not move.release-pending-publish.mjsimports the leaf, so its./x.mjsclosure is nowboard-read.mjsandinvoked-as.mjsonly. Importing the citation gate would have pulled inpm/check-half-states.mjsand the rest of its closure, whichrelease-verify-npm.mjs --self-testcannot copy into its fixture repo. That was this PR's first CI red.H2: why the sweep keeps listing completed run 40078805767
By design, so this PR leaves it as it is.
collectWaitingRunsfinds each version commit's push run byhead_sha, walking the commits newest first, and stops at the first run older thanWAITING_READ_BACK_DAYS(30). It then checks that each run it found carries that version's publish job:(
scripts/release-pending-publish.mjsL1228). This came in witheead9dcf40, the commit that added the targeted read. Its message says: "It checks that each run it finds carries that version's publish job". The self-test pins it: thequietcase reads completed runs 301 and 302, and thewrongcase flags completed run 501, which has no publish job.Read-only GETs show:
e649c2d9("chore: version packages (chore: version packages #25606)"), created 2026-10-24, completed / success.e5136ff1("chore: version packages (next) (chore: version packages (next) #23481)"), created 2026-10-16.Both are inside the 30-day window. So every push re-reads the jobs listing of every version-commit push run from the last 30 days. 40078805767 is simply the newest of them, and it is read on every sweep until about 2026-11-23. The 502 lands on whichever of those jobs listings GitHub fails to answer. The run itself is not special: a public read of its jobs listing answered 200 with 6 jobs in 1.4 s. With this PR, a 502 on any of those reads is retried, and if it persists the sweep is deferred instead of turning red.
Tests
The self-test has a new battery:
the sweep -> a transient read is retried under one budget, still failing it defers with a warning and exit 0, and a 4xx or a failed cancel stays red. It has a floor of 10, and the battery floor went from 34 to 35. Its cases drive the wholesweepoffline with a stubbedhttpand a real summary file in a temp dir:::warning::line naming the read,HTTP 502 after 3 attempts, and aNOT READsummary section;ENOTFOUND): rethrown on the first attempt, so the job goes red;in-flight) on a 502: stillunreadable, soin-flight, after one request.The existing pin
a probe answering non-200 -> the read throws (a red job)still passes unchanged on the bare layer.self-test at 88c63cd
(The baseline at 361ea24 was 136 cases across 34 batteries.)
Ablations, both via scripts/ablation-replace.mjs, restored byte-identical
if (method !== 'GET') return http(method, path);replaced by an unconditional pass-through): the mutation landed (anchor 1 to 0, blob186cbaab511dtofb6db796de53), and the self-test went red with 5 failures. Among them were "a 502 then a 200 ... swept", "a 502 on every attempt ...", "a connection reset ... swept" and "a flapping API ...". Restored: blob equals HEAD186cbaab511d, andgit diff HEADis empty.TransientReadFailurecatch rethrows every error): the mutation landed, and the self-test went red with 3 failures ("a 502 on every attempt ...", "and it says so ...", "a flapping API ..."). Restored byte-identical to HEAD.Acceptance notes
.github/workflows/release.yml'sstale-promptscomment says "a failed read here must be loud (a red job on main)". After this PR, a read that keeps failing transiently is a::warning::annotation plus a summary section, not a red job. Every other failure is still red. The comment is in a Tier H file, so this PR does not edit it. carrier: none.ECONNRESET,UND_ERR_SOCKET). Other connection failures, such asENOTFOUNDandETIMEDOUT, stay final for the sweep exactly as they do for the release gate. Widening the list is a change to the shared policy, not to this script.sweepwas run. All proof comes from the stubbed harness.scripts/ci/scheduled-full-run.mjsstill imports the policy throughcheck-issue-citations.mjs, so the citation gate's PM closure still loads in thefull-batteryjob. Switching it to./board-read.mjsis a one-line follow-up, left out of this PR on purpose. carrier: none.Verification
I ran the local gates at
88c63cd785, the final commit. The derived list came fromnode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 34 commands, and all 34 exited 0. They includenode scripts/release-pending-publish.mjs --self-test,node scripts/check-issue-citations.mjs,pnpm check:issue-citationsandpnpm check:pm-dispatch-gates(fast tier, 1937 cases). Thedispatch-gates --ranreconciliation answered34 derived famil(ies) accounted for — 34 run, 0 NOT-MEASURED (a DERIVED zero — all 34 recorded an exit code and none of them is 3). Outside the derived set, these also exited 0:node scripts/release-verify-npm.mjs --self-test(169 cases, 16 batteries),node scripts/check-issue-citations.mjs --self-test,node scripts/ci/scheduled-full-run.mjs --self-test, andeslint --no-inline-configon the three changed files. No changeset:scripts/publishes nothing, henceskip-changeset.Generated by Claude Code