Repository navigation
fix(release): re-derive release-verify-npm read-back budget from the 18.0.0 reading (35 min, flat) - #24989
Merged
objectstack-fleet[bot] merged 1 commit intoOct 20, 2026
Conversation
…e 18.0.0 reading The 18.0.0 publish read red with every package on npm: @objectstack/console became readable at 12:57:59.327Z, 16m22s after the first read and 21 s after the 15-minute budget ran out. The budget is re-derived with the 17.3.0 margin (900 s / 427 s = 2.11x) over the 18.0.0 spread: 982 s x 2.11 = 34m30s, so RETRY_BUDGET_MS is 35 minutes. It stays one flat number: on 18.0.0 the largest tarball (spec, 42 MB packed) settled six minutes before console (9.4 MB), so size does not predict who settles last. It still fails closed. main() now takes the verifier's probe and sleep seams, so battery 14 pins its exit code and its ::error:: naming on npm's replayed clock: the 18.0.0 window reds under the old 900 s, goes green under the production budget, and an absent package exits 1 named after the whole budget. Claude-Session: https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp Co-authored-by: Claude <noreply@anthropic.com>
Contributor
Author
Seat reviewACCEPT ·
This seat readies the PR and queues it with auto-merge. Landing it publishes nothing. The next publish is the first that reads under the new budget. Generated by Claude Code |
objectstack-fleet
Bot
deleted the
claude/issue-24888-verify-npm-budget
branch
October 20, 2026 22:30
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 free
to 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 #24888
Clause-②: no
scripts/release-verify-npm.mjswaited 15 minutes for npm's read path to settle. On 18.0.0 the last package became readable 16m22s after the first read, so a release that was complete read red. This PR re-derives the budget from the 18.0.0 reading and sets it to 35 minutes, one flat number for the group. It still fails closed. The self-test now replays the 18.0.0 window on npm's own clock and pinsmain()'s exit code.The measurements, re-read
3fbef41b4a)publish ran but 1 of 73 package(s) are still not readable on npm after 19 round(s) over 900s: @objectstack/console@18.0.0consolereadablenpm view @objectstack/console time --json"18.0.0": "2026-10-20T12:57:59.327Z"The start time is not re-read here. The job-log download redirects to a blob host that this container's egress proxy refuses (403, a policy denial, not retried). The readings above corroborate it:
timefor all 73 packages gives 408 probes. The card's start implies 61 s of probing, about 150 ms per probe.Spread: 12:41:37Z to 12:57:59.327Z = 982.3 s (16m22s).
Sizes come from npm's
distfields and the tarball bytes, downloaded:@objectstack/console@objectstack/specThe card's "42424 KB" for
consolelives in the job log, so it could not be re-read. It is notconsole's tarball, which is 9.4 MB. The release's largest tarball isspec's.Shape: flat, 35 minutes. The deciding axis: actual business need (实际业务需求)
Derivation. 17.3.0's margin was 900 s / 427 s = 2.11x. The same margin over the new spread is 982.3 s x 2.11 = 2070 s (34m30s). Rounded up, that is 35 minutes (2100 s, 2.14x the 18.0.0 spread). The header records this next to the 17.3.0 derivation.
spec(42.1 MB packed, the largest) was readable six minutes beforeconsole(9.4 MB). On 17.5.0 the order was reversed (console08:05:24Z,speclast at 08:09:33Z), and on 17.3.0 the last package wasruntime. Any monotonic size scale gives the slowest 18.0.0 package a smaller allowance than one that settled six minutes earlier.The budget is a ceiling, not a wait. The loop returns on the first round that finds the last target, so a good publish pays only its own settle time. Replayed on this schedule, 18.0.0 goes green at +17m15s (battery 14 pins that figure). Only a publish that is really broken spends all 35 minutes.
Fit against the caller
release.ymlwas read, not edited. Neither thepublishjob (:1565to:2132) nor its step "Publish to npm + push version tags" declarestimeout-minutes. The onlytimeout-minutesin the file isfull-battery's 10, at:1536. GitHub's default therefore bounds both: 360 minutes per job. Therelease-integrityaudit calls the script with--probe(:1251). That path passes a budget of 0 and is unchanged; battery 10 pins its single round.Measured on 18.0.0:
pnpm run release: publish plus tag push)Worst case under the new budget, using budget plus poll interval plus the rest of the step:
The same terms at the old budget gave 38m46s.
One case lies outside that formula and existed before this change: a registry where every request hangs to the 15 s per-request ceiling. Each round then costs 73 x 15 s = 18m15s. That totals 347 minutes of probing at the old 19 rounds and 712 minutes at the new 39. In both cases GitHub's 360-minute default cancels the job. The job is red, so the run still fails closed, but the cancellation does not name a package. The outcome is the same before and after this change.
Self-test and ablation
The self-test goes from 115 cases across 13 batteries to 131 cases across 14 batteries, with every battery floored.
Battery 1 gains two cases: the budget outlasts the 18.0.0 spread, and it keeps the 2.11x margin over it.
Battery 14 ("The 18.0.0 publish window: console readable at +16m22s, past the old 900 s") has 14 cases. It replays npm's own
timeforsdui-parser,cli,specandconsoleon a clock that the stubsleepdrives. Probing is taken as instant, so a green in the replay is one the real step would also have seen. No case sleeps.console, after 19 rounds over 900 s, which matches the run's annotation. Its last read ofconsolecame beforeconsolelanded.consolelanded, at +17m15s, past the round where 900 s ran out.main()over a fixture workspace exits 0 and prints the::notice::line.consolenever landing,main()exits 1. Its::error::line names@objectstack/console@18.0.0and "1 of 4", and names none of the three packages that landed. It concludes only after the waits sum to exactlyRETRY_BUDGET_MS.This needed one seam change.
main()now accepts the verifier's ownprobeandsleepseams; the defaults are the production ones. Its exit code and naming can therefore be pinned without the network or a real wait.The case of a large and a small package, each held to its own allowance, does not apply, because there is no per-package allowance.
Ablation. I committed the fix first (
a9e61579a3). I then ranscripts/ablation-replace.mjsin wrap mode, replacingexport const RETRY_BUDGET_MS = 35 * 60 * 1000;with15 * 60 * 1000;.7f259d67ff62becamec36e8cf51a07.node scripts/release-verify-npm.mjs --self-testthen exited 1 with 8 of 131 cases failed, exactly the cases that need more than 900 s (one JSON detail is shortened):The red went in the expected direction. The old-budget repro and the absent-package cases stayed green, as they should.
The restore was proven on disk: the blob after the restore equals the HEAD blob (
7f259d67ff62),git diff HEADwas empty andgit status --porcelainprinted nothing.Changeset:
skip-changeset, measuredscripts/release-verify-npm.mjsbelongs to the root package@objectstack/spec-monorepo, which isprivate: trueand has nofiles. No non-private workspace package lists afiles[]entry underscriptsor with... No tracked symlink points into the rootscripts/.git grep -l RETRY_BUDGET_MSoverpackages/**,apps/**andskills/**returns 0 hits (exit 1). The positive control overscripts/**returnsscripts/release-verify-npm.mjs(exit 0).Gates (local, at
a9e61579a3)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 33 commands for this one-file diff. All 33 ran and each exited 0,check:pm-dispatch-gatesincluded (fast tier, 1913 cases).--ranreconciliation:33 derived, 33 run, 0 NOT-MEASURED, 0 UNRUN.npx eslint --no-inline-config scripts/release-verify-npm.mjsexited 0.git merge-tree --write-tree origin/main HEADatorigin/main78ab6474caexited 0. The commits since the base touch none of this script,release-github-releases.mjs,workspace-enumerator.mjsorrelease.yml.CI owns the rest of the farm.
lint.yml's "Post-publish npm verification self-test" step runs this self-test.Acceptance notes
Stale comments in a governed file, not edited here. Two comments in
.github/workflows/release.ymlstill describe the old budget::2077to:2080says the verifier "retries with bounded backoff for 15 minutes, derived from ... 7m07s";:1236says "instead of waited on for 15 minutes".Both now read stale.
.github/workflows/**is governed, so this needs a separate comment-only edit if wanted. Carrier: none.Pre-existing. The hung-registry worst case above exceeds GitHub's 360-minute default at either budget, and is cancelled red without naming a package. This is an inference from the constants, not a reproduction. Carrier: none.
Generated by Claude Code