Repository navigation
fix(release): the image backfill skips a version whose publishing run has not finished building its image - #21634
Merged
objectstack-fleet[bot] merged 2 commits intoOct 3, 2026
Conversation
…n's image build is finished `judgePublishInFlight` now carries a `docker` verdict beside the publish job's: a run holding the version's publish job past the `release` environment builds the image until every docker job of that run is `completed`, including before GitHub creates it (it needs the publish job). An unreadable read answers `in-flight` / `unreadable` for both. New self-test battery (12 cases); the roster floor rises to 22. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
… has not finished building its image The audit step asks the in-flight reader once, before either backfill, and gates `image-missing` on its `docker` verdict the way it gates `releases-missing` on the publish job's: a notice while the publishing run's docker job is not yet created, queued or running; a warning, and no request, when the read is unreadable or cannot be asked; the existing request otherwise. Battery 12 of release-verify-npm's self-test pins the four triage pins plus the not-yet-created case at the audit's own text. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
objectstack-fleet
Bot
deleted the
claude/issue-21606-image-backfill-guard
branch
October 3, 2026 21:56
This was referenced Oct 4, 2026
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 #21606
Clause-②: no
What this does
release-integrity's audit step now skips the image backfill for a version whose publishing run has not finished its own image build. It still skips the Releases backfill for a version whose publish is in flight, as before. Both answers come from one read of PR #21605'sin-flightreader. While the publishing run'sdockerjob is not yet created, queued or running, the step leavesimage-missingunset and prints a notice naming that run. When the read is unreadable, or cannot be asked at all, it leavesimage-missingunset and prints a warning. Otherwise it takes the existing path. The guard skips a write and refuses nothing: the step stays green, and no gate is added.scripts/release-pending-publish.mjs:judgePublishInFlightreturns adockerverdict beside the publish job's top-level one (in-flightwith reasondocker-in-flightorunreadable, orclear).publishInFlightanswersunreadablefor both whenever the read fails.collectPublishRunsis unchanged, because it already reads the jobs of every candidate run that is not completed. This adds one self-test battery (12 cases), and the roster floor rises from 21 to 22..github/workflows/release.yml: only the audit step changes. It asks the reader once, before either backfill branch, and gatesimage-missingon.docker.statethe same way it gatesreleases-missingon.state. No other job, step or permission changes;actions: readcame with PR fix(release): the Releases backfill skips a version whose publish is in flight in another run #21605.scripts/release-verify-npm.mjs: only the self-test harness changes. Battery 12 executes the audit step's text. Its Actions stub gains the publish job's status and the docker job's status, and the battery gains 10 cases (floor 24 to 34). The battery name and the success line now mention the image build.The premise, measured (read-only
GETs on the runs and jobs)created_at36955885276(push atdcc5ef4c, the publishing run)Release integrityPublish 17.6.0 to npm (awaiting approval)110678824190Docker image / Build & push ghcr.io/objectstack-ai/objectstack11068709766636958423332(push at4e530568, the landing)Release integrity110686494582Docker image / Build & push ...110687103770(the backfill)needssettle. The publish job was created one second afterrelease-integritycompleted. The docker job was created the same second the publish job completed. So from 02:30:34Z to 03:05:45Z the publishing run held no docker job at all. The landing's audit read the image as missing at about 03:04Z, and at that instant the run's jobs list held only the publish job,in_progress. PR fix(release): the Releases backfill skips a version whose publish is in flight in another run #21605's answer isclearas soon as the publish job completes, and from that second until the docker job completes the image is still being built. So "not yet created" is a real state, and the reader counts it as in flight.Docker image / Build & push ...while its reusable workflow runs, andDocker imagewhen it is skipped (measured on run37148267152, created 19:33:23Z with the skipped publish job). The reader matches both names (DOCKER_JOB_NAME).judgePublishInFlightandpublishInFlight, not a second reader. The same candidate runs and the same reads produce both answers.node scripts/release-verify-npm.mjs --self-testprintedOK ... 103 cases pass, with no red at all (PR fix(release): the Releases backfill skips a version whose publish is in flight in another run #21605 got 2 reds at that point). The stubs already answered the Actions read, and no existing pin assertedimage-missingwhile a publish was in flight. That is why battery 12 gains the pins below: ablation 1 shows that they, and nothing else, catch the new branch being disabled. Battery 13 needed no change. Its audit reads the image as present, so the image branch never requests anything there.The guard's rule
A run is the version's publishing run when it holds
Publish V to npm (awaiting approval)in any status butwaiting(approved, running or done). The calling run is excluded, as for the Releases guard. In such a run that is not completed, the image is in flight until every docker job of the run iscompleted, including while it has none yet. Pinned per triage's pins (5971281558):release-pending-publish, the new battery)release-verify-npmbattery 12)queued/in_progress/waiting/ unknown under either name; the whole read through the push run at the version commit; the repair lane through thein_progressfilterin_progress: noimage-missing, plus a notice naming run 4242 and "not yet created"; publish completed with no docker job (Releases present): noimage-missing; dockerin_progress: noimage-missingwhile the Releases are backfilledclearimage-missing, "image ... is present"clear, so the image probe alone decidesimage-missing=trueunreadableimage-missing, and a warning; the reader unable to run at all: no backfill, and a warning for bothBoth columns also pin the boundary. A publish held at the
releaseenvironment builds nothing before a human approves it, so it isclearfor the image too; this matches the Releases guard and keeps a stale prompt from blocking the backfill. Another version's publish, the calling run, and a run whose skipped publish job names no version are allclear.Tests (at
4d31b13409, the final commit)node scripts/release-pending-publish.mjs --self-test:✓ release-pending-publish self-test: 92 cases across 22 batteries pass.That is 80 before this PR, plus the new battery's 12.node scripts/release-verify-npm.mjs --self-test:OK release-verify-npm self-test: 113 cases pass across 13 batteries. That is 103 before, plus 10.scripts/ablation-replace.mjsin wrap mode. Each anchor hit once and went from 1 to 0, and each restore was proven by blob equal to HEAD and an emptygit diff HEAD:elif [ "$(jq -r '.docker.state' ...)" != 'clear' ]; thenbecameelif false; then).release-verify-npm --self-testwent 5 of 113 red: unreadable, then a backfill; the publish in flight, then a backfill; the notice missing; the publish done with no docker job yet, then a backfill; dockerin_progress, then a backfill. The blob went451300d30c34to69bb917dd3b4and was restored to451300d30c34.docker.length > 0 &&deleted).release-pending-publish --self-testwent 3 red (the 03:04Z instant, the not-yet-created instant, the whole read), andrelease-verify-npm --self-testwent 3 of 113 red through the same reader. The blob wentb8fc0ce80442to3efaba0b79ceand was restored.dockerwas madeclear.release-pending-publish --self-testwent 1 red (the three unreadable reads), andrelease-verify-npm --self-testwent 1 of 113 red (the 503 case). The blob wentb8fc0ce80442to523a9ea699c4and was restored.publishInFlightwas called for 17.6.0 with a GET-only client. It made 5 GETs: thein_progressandqueuedfilters (both empty),head_shaprobes at617f25f8a4(none) anddcc5ef4c5a(run36955885276), and a direct read of that run, which iscompleted. The answer wasclear/clear. Replaying the real job list of run36955885276cut at 03:04:15Z gavein-flightfor both; at 03:05:45Z, with the docker job just created, it gaveclearfor the publish andin-flightfor the image.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat4d31b13409derived 51 commands. All 51 were run, each exited 0, recorded per command before reading.--ranprinted:Run reconciliation — 51 derived, 51 run, 0 NOT-MEASURED, 0 UNRUN.eslint --no-inline-config --format jsonover the two changed.mjsfiles: 2 files, 0 errors, 0 warnings.isPathIgnoredandcalculateConfigForFileput both files in the linted population.release.ymlis ignored, so it has no config.parserOptions.projectand noprojectService. Type-aware linting is off, so this diff cannot move any verdict on an untouched file.ci.ymlTest Core's shard steps, and the familiesdispatch-gateslists as wide-population or workflow-valued (the latter printed as NOT MEASURED there).Changeset
None.
skip-changesetholds because nothing here is published. The diff is.github/workflows/plus two repo-rootscripts/files, and the rootpackage.jsonis private. None of the 69 public packages'files[]namesscripts/or.github/. As a positive control, all 69 of them namedist.Acceptance notes
release-integrityfinishes, and it can read the image as missing while that docker job still builds. The guard reads only the publishing run, as triage directed. It cannot attribute a backfill run's docker job to a version, because the job name carries none. Nothing is measured: no run has been named. Carrier: none.release-integrity'soutputs.publishedcomment ("set only when npm ALREADY has this version's WHOLE fixed group and its runtime image is missing") is left as it is. It names necessary conditions and stays true, and the claim limitsrelease.ymlto the audit step. The new condition is stated in the step.dockerjob thatDOCKER_JOB_NAMEmisses reads as "not yet created". The image then counts as in flight until the publishing run completes: late, never early. The constant's docblock says so.lint.yml's comment above "Release version-commit selection self-test" says the script "holds all three decisions". PR fix(release): the Releases backfill skips a version whose publish is in flight in another run #21605 already noted it.lint.ymlis outside this card's surface. Carrier: the next PR that edits that step's comment.Generated by Claude Code