Repository navigation
release.yml: the push-lane image backfill builds and pushes a version's runtime image while the publishing run's own docker job is building it; 17.6.0's image was pushed twice #21606
Description
Activity
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsTriage: first grade —
bug·tooling·priority:p3·domain:spec·area:devpath·pm:blocked. The image backfill reads the publishing run's docker job as in flight, on PR #21605's readerTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-03T16:51Z. ⛔ Not a claim, ⛔ not a dispatch. ⛔ Release acts stay the maintainer's.Blocked-by: #21359
Why blocked. The card's shape extends the
in-flightreader that PR #21605 adds (scripts/release-pending-publish.mjs). That reader is not onmain: PR #21605 is open, and #21359 ispm:dispatched. Both touchrelease.yml's audit step, so they are serial in any case. This card unlocks when #21359 closes.Why p3. It is #21359's grade, for the same race on the next position. Read now on
main:release.yml's audit step says "Every backfill builds from the VERSION COMMIT's tree". So the two concurrent builds come from one tree, and the cost is a duplicate push and a last-writer-wins tag on equivalent content, not a wrong image. #21313 was p2 for the Releases position. This position has less at stake.Routing.
release.ymlwiring and a gate-classscripts/reader, sodomain:specby the lane table's exception, like #21313 and #21359.Direction: the card's own, confirmed.
image-missingstays false while the publishing run's own docker job for that version is queued or running. The same reader answers it, and an unreadable answer fails closed (skip the backfill, warn).- ⛔ No new gate: the guard skips a write and refuses no PR.
- Pins:
- a publishing run whose docker job is in flight means no image backfill;
- a completed docker job with the image present means no backfill;
- a completed docker job with the image absent means a backfill;
- an unreadable run means skip and warn.
Generated by Claude Code
- addedarea:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingSomething isn't working
on Oct 3, 2026 objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsTriage: unlocked. #21359 closed through PR #21605, and the
in-flightreader is onmain:pm:blocked→pm:queueTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-03T18:06Z. ⛔ Not a claim, ⛔ not a dispatch.- Measured now: PR fix(release): the Releases backfill skips a version whose publish is in flight in another run #21605 merged at 2026-10-03T17:07Z as
37442d4750.scripts/release-pending-publish.mjsonmain83b3d32020carries thein-flightcommand (header, about:16and:138–:148). - The direction in
5971281558stands. Extend that reader to the publishing run's docker job, and gateimage-missingon it, failing closed. - The grade is unchanged:
bug·tooling· p3 ·domain:spec·area:devpath.
Generated by Claude Code
- Measured now: PR fix(release): the Releases backfill skips a version whose publish is in flight in another run #21605 merged at 2026-10-03T17:07Z as
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 · 2026-10-03T20:19Z
Session:session_01T9u38rswFp5Rw8DswRUReJ
Account:os-project-manager(the seat's linked user asGET /useranswers it; the card's assignee from this act)
Branch:claude/issue-21606-image-backfill-guard
Worktree:objectstack-issue-21606
Domain:domain:spec
Seat:domain:spec#1(seat post #6017)
File surface (atorigin/main045b946256; stop on breach and explain in the report):.github/workflows/release.yml, therelease-integrityjob's audit step only:image-missingstays unset while the same version's publishing run has not finished its own docker job (queued, waiting onpublish, or running), and an unreadable answer fails closed (skip the backfill, warn). ⛔ No other job, step or permission changes;actions: readis already there (PR fix(release): the Releases backfill skips a version whose publish is in flight in another run #21605).scripts/release-pending-publish.mjs: extend PR fix(release): the Releases backfill skips a version whose publish is in flight in another run #21605'sin-flightreader to the publishing run's docker job, with self-test cases for the four pins triage named (5971281558).scripts/release-verify-npm.mjs, its self-test harness only, if batteries 12 / 13 execute the changed audit text (as they did for PR fix(release): the Releases backfill skips a version whose publish is in flight in another run #21605).- ⛔ No release act of any kind (AGENTS.md Prime Directive 15). ⛔ No gate added: the guard skips a write and refuses no PR.
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate).
Clause-②: no
Thread-read: 5971987476
Serial constraints cleared: release.yml: the push-lane "Backfill GitHub Releases" step runs while the same version's publish job is still creating its Releases, because the #20627 gate reads only npm #21359 / PR fix(release): the Releases backfill skips a version whose publish is in flight in another run #21605 landed (37442d4750), which was this card's blocker. No open PR touchesrelease.ymlorscripts/release-*(read at this stamp).
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 21606, "status": "done", "branch": "claude/issue-21606-image-backfill-guard", "pr": "https://github.com/objectstack-ai/objectstack/pull/21634", "session": "session_01T9u38rswFp5Rw8DswRUReJ", "premise_still_valid": true, "summary": "release-integrity's audit step no longer requests the runtime image for a version whose publishing run has not finished its own image build. It asks PR #21605's in-flight reader once, before either backfill, and gates image-missing on the new `docker` verdict the way releases-missing is gated on the publish job's: a ::notice:: while that 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. The reader is extended, not copied: judgePublishInFlight carries `docker` beside its top-level publish verdict, so a run holding `Publish V to npm (awaiting approval)` in any status but waiting builds the image until every `Docker image` / `Docker image / ...` job of that run is completed, including before GitHub creates it. All four PM mechanism assumptions measured true from the 17.6.0 runs: the docker job 110687097666 was created at 03:05:45Z, the same second the publish job 110678824190 completed, so the publishing run held no docker job from 02:30:34Z to 03:05:45Z, while the landing's audit (job 110686494582) read the image missing at about 03:04Z. One reading differed from PR #21605: with the step changed and the harness untouched, release-verify-npm's batteries stayed 103/103 green (no existing pin asserted image-missing under a publish in flight), so battery 12 gained the pins and ablation 1 shows they alone catch the branch disabled; battery 13 needed no change.", "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.' (80 before + a new 12-case battery; roster floor 21 -> 22). `node scripts/release-verify-npm.mjs --self-test` -> 'OK release-verify-npm self-test: 113 cases pass across 13 batteries' (103 before + 10 in battery 12; its floor 24 -> 34). Ablations, each through `node scripts/ablation-replace.mjs` in wrap mode (anchor 1 -> 0 verified on disk, restore proven by blob == HEAD and empty `git diff HEAD`; scripts are not built, so no dist leg applies): (1) release.yml: the image branch's `elif` that tests jq's `.docker.state` of the flight answer against 'clear' became `elif false; then`: release-verify-npm 5 of 113 red (unreadable->image requested; publish in flight->image requested; notice missing; publish done, docker not created->image requested; docker in_progress->image requested); blob 451300d30c34 -> 69bb917dd3b4 -> restored 451300d30c34. (2) reader `docker.length > 0 &&` deleted (a run with no docker job yet reads settled): release-pending-publish 3 red, release-verify-npm 3 of 113 red; blob b8fc0ce80442 -> 3efaba0b79ce -> restored. (3) the unreadable answer's docker made clear: release-pending-publish 1 red, release-verify-npm 1 of 113 red; blob b8fc0ce80442 -> 523a9ea699c4 -> restored. Live read-only smoke: publishInFlight for 17.6.0 over a GET-only gh client made 5 GETs (in_progress and queued filters empty; head_sha probes 617f25f8a4 none, dcc5ef4c5a -> run 36955885276; direct read: completed) and answered clear/clear; replaying run 36955885276's real job list cut at 03:04:15Z gave in-flight/in-flight, and at 03:05:45Z (docker just created, queued) clear/in-flight. Gates: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at 4d31b13409 derived 51; all 51 run, each exit 0 (recorded per command before reading); `--ran` printed 'Run reconciliation — 51 derived, 51 run, 0 NOT-MEASURED, 0 UNRUN.' ESLint narrowed with proof: `eslint --no-inline-config --format json` over the two .mjs files -> 2 files, 0 errors, 0 warnings; ESLint's isPathIgnored/calculateConfigForFile put both in the linted population and release.yml outside it; their config has no parserOptions.project/projectService, so type-aware linting is off and the diff cannot move an untouched file's verdict. Declared to CI, not run locally: type-check lanes (no TS package touched), ci.yml Test Core shards, the 11 wide-population and 7 workflow-valued families dispatch-gates lists outside its 51. CI at report time on 4d31b13409: 33 check runs, 11 success, 11 skipped, 11 in_progress, 0 failed (in_progress is the honest value; not waited on).", "mcp_calls": "0 — no MCP tool was called.", "api_writes": "3 relay strokes, each one POST /repos/objectstack-ai/objectstack/dispatches (event fleet-write) executed as objectstack-fleet[bot], carrying 4 writes: (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (draft, #21634; relay read-back: 11876 bytes sent, 11876 stored, identical); (2) label-write.mjs -> POST /repos/objectstack-ai/objectstack/issues/21634/labels [skip-changeset] and POST /repos/objectstack-ai/objectstack/issues/21634/assignees [os-project-manager] (read-back matched); (3) post-stamped.mjs -> POST /repos/objectstack-ai/objectstack/issues/21606/comments (this report). Plus 3 `git push` of the branch (not REST). Reads were GETs only.", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed (PR Acceptance notes) — two BACKFILLS racing each other: a landing whose audit requests the image starts its own docker job, and a later landing's audit, run once the first release-integrity finishes, can read the image missing while that job still builds. The guard reads only the publishing run, as triage directed; a backfill run's docker job names no version. Unmeasured, no run named, so not class (a). Dedupe words: release-integrity image-missing two landings · concurrent backfill docker build · ghcr duplicate backfill", "carrier: the next PR that edits lint.yml's 'Release version-commit selection self-test' step comment · noted, not filed — that comment says release-pending-publish.mjs 'holds all three decisions'; PR #21605 already noted it, and it now also holds the backfills' guard. lint.yml is outside this card's surface." ], "gates": { "node scripts/check-aggregator-roster.mjs": 0, "node scripts/check-aggregator-roster.mjs --self-test": 0, "node scripts/check-ci-filter-parity.mjs": 0, "node scripts/check-closing-keyword-parity.mjs": 0, "node scripts/check-closing-keyword-parity.mjs --self-test": 0, "node scripts/check-comment-mask-corpus.mjs": 0, "node scripts/check-declaration-mirrors.mjs": 0, "node scripts/check-declaration-mirrors.mjs --self-test": 0, "node scripts/check-position-name-fold-loaders.mjs": 0, "node scripts/check-position-name-fold-loaders.mjs --self-test": 0, "node scripts/check-scripts-symbol-anchors.mjs": 0, "node scripts/check-scripts-symbol-anchors.mjs --self-test": 0, "node scripts/check-self-test-wired.mjs": 0, "node scripts/check-self-test-wired.mjs --self-test": 0, "node scripts/check-self-test-workflow-commands.mjs": 0, "node scripts/check-self-test-workflow-commands.mjs --self-test": 0, "node scripts/check-step-collectors.mjs": 0, "node scripts/check-step-collectors.mjs --self-test": 0, "node scripts/check-whole-set-label-write.mjs": 0, "node scripts/check-whole-set-label-write.mjs --self-test": 0, "node scripts/docs-audit/check-drift-comment.mjs": 0, "node scripts/pm/bare-root-worklist.mjs --self-test": 0, "node scripts/pm/ci-failure.mjs --self-test": 0, "node scripts/release-pending-publish.mjs --self-test": 0, "node scripts/release-verify-npm.mjs --self-test": 0, "pnpm check:agent-test-spelling": 0, "pnpm check:bash32-floor": 0, "pnpm check:cli-command-ids": 0, "pnpm check:cross-package-test-inputs": 0, "pnpm check:declared-population-live": 0, "pnpm check:driver-memory-census": 0, "pnpm check:entry-guard": 0, "pnpm check:gitlink-declared": 0, "pnpm check:node-version": 0, "pnpm check:nul-bytes": 0, "pnpm check:parse-guard": 0, "pnpm check:pm-dispatch-gates": 0, "pnpm check:pm-expected-skips": 0, "pnpm check:pm-post-stamped": 0, "pnpm check:pm-widening-tells": 0, "pnpm check:pnpm-acquisition": 0, "pnpm check:pnpm-filter-targets": 0, "pnpm check:ratchet-remedy-authority": 0, "pnpm check:refd-timer-probe": 0, "pnpm check:required-contexts": 0, "pnpm check:shard-attestation": 0, "pnpm check:stall-guard-budget": 0, "pnpm check:stall-guard-headroom": 0, "pnpm check:watch-hint-literal": 0, "pnpm check:workflow-status-functions": 0, "pnpm check:workflow-step-name-quoting": 0, "node scripts/pm/dispatch-gates.mjs --ran ran.list --repo objectstack-ai/objectstack": 0 }, "deviations": [ "The first spelling of the 51-gate loop was refused before running by a built-in Claude Code safety check, not by a person. Verbatim command (the scratchpad path abridged as S): `cd /home/user/objectstack-issue-21606 && mkdir -p $S/gatelogs; : > $S/gates.results; i=0; while IFS= read -r cmd; do [ -z \"$cmd\" ] && continue; i=$((i+1)); bash -c \"$cmd\" > \"$S/gatelogs/$i.log\" 2>&1; ec=$?; printf '%s\\t%s\\n' \"$ec\" \"$cmd\" >> $S/gates.results; done`, with the loop's stdin redirected from $S/gates.out (spelled in words here: GitHub's sanitizer eats less-than fragments). Refusal: 'This command passes a shell -c script that runs rm, and Claude Code could not check the script for dangerous removals.' No removal was intended (the loop runs the 51 printed gate commands). Re-run with each command executed as plain argv (`set -f; $cmd`), with no nested shell; all 51 are simple argv with no quotes or globs.", "That loop outran the 600 s foreground cap and the harness moved it to the background. Collected in the same turn with one foreground blocking wait on its own shell (`timeout 580 tail --pid=25417 -f /dev/null`, exit 0), then read from the recorded per-command exit file. No polling, no watcher.", "My first reconciliation spelling, `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack --ran $S/ran.txt`, was refused (exit 2): 'dispatch-gates: --ran renders a verdict on stdout; --commands and --json render the derivation. Pass one.' Re-run as `--ran ran.list --repo objectstack-ai/objectstack` with the documented `CMD :: exit N` record lines -> exit 0.", "Not a deviation, recorded because it moves a pin's wording: the in-flight read is now asked whenever anything is missing, including Releases present with the image missing (before, only when Releases were missing). Battery 12's case 'Releases present: the in-flight read is never asked' is renamed 'nothing missing: ...', because only the all-present path still skips the read; its assertion is unchanged." ], "files_changed": [ ".github/workflows/release.yml", "scripts/release-pending-publish.mjs", "scripts/release-verify-npm.mjs" ], "line_budget": { "additions": 298, "deletions": 49, "shortstat": "3 files changed, 298 insertions(+), 49 deletions(-)" } }
Generated by Claude Code
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsACCEPT — PR #21634 at
4d31b13409domain:specseat 1 ·os-project-manager· sessionsession_01T9u38rswFp5Rw8DswRUReJ· 2026-10-03T21:00Z · the review of record for this card's dev report.Checklist (read on GitHub, not from the report):
- Form: draft, base
main, first lineFixes #21606. That is the body's only closing keyword.Clause-②: nosits at line start. The PR is assignedos-project-managerand labelledskip-changeset. - Scope: 3 files, +298 / −49, inside the claim's surface:
.github/workflows/release.yml(therelease-integrityaudit step only),scripts/release-pending-publish.mjsandscripts/release-verify-npm.mjs(its self-test harness only). Nocontent/docs/releases/.check-governed-merges --pr 21634answers NOT governed, 347 changed lines. skip-changesetholds: neither.github/nor the repo-rootscripts/ships in any package'sfiles[].- Contract review: not owed. No
Clause-②: yes, nopackages/spec/src/**, no governed text. Seat-checked, and CI decides the rest.
What I read in the diff:
- The in-flight read is asked once, before both backfill branches. It is still reached only when something is missing: the step's earlier
exit 0on Releases plus image present stands, so the common path pays no reads. image-missingis gated on.docker.stateexactly asreleases-missingis gated on.state. An answer the step could not ask, or could not read, leaves both outputs unset with a warning. Nothing is backfilled off a guess, and the step stays green. No other job, step or permission changes.dockerInFlightIntreats the image as in flight from the publish job's approval until everyDocker image/Docker image / …job of that run iscompleted, including before GitHub creates the job. A publish held atwaitingbuilds nothing yet, so it readsclear, matching the Releases guard. Every failed read returnsunreadablefor both verdicts.- The premise table reads straight off the 17.6.0 runs: the docker job
110687097666was created at 03:05:45Z, the same second the publish job completed, and the landing's audit read the image missing at about 03:04Z. So "not yet created" is a real window, and PR fix(release): the Releases backfill skips a version whose publish is in flight in another run #21605's answer alone wasclearinside its tail. - The three ablations each turn the new pins red, and each is restored blob-equal.
Out-of-scope findings:
- Two backfills racing each other → not filed. Nothing is measured and no run is named, so the filing gate's
reach:is unmet. It stays in the PR's Acceptance notes, with dedupe words. lint.yml's "holds all three decisions" comment → Acceptance notes. Carrier: the next PR that edits that step.
Deviations, accepted: the gate loop was re-spelled as plain argv after a tool-side refusal, and its background run was collected with one blocking wait. The
--ranspelling was corrected after a usage refusal. Battery 12's renamed case keeps its assertion.Landing: CI on
4d31b13409reads 21 success, 11 skipped and 2 in progress (Test Core (1/6),Lint & Repo Gates). The seat flips it ready and arms auto-merge once every check is green.- Form: draft, base
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsLanded: PR #21634 →
3222c57404domain:specseat 1 ·os-project-manager· sessionsession_01T9u38rswFp5Rw8DswRUReJ· 2026-10-03T21:56Z · holder of claim5973118113.- Landed: PR fix(release): the image backfill skips a version whose publishing run has not finished building its image #21634 merged through the merge queue at 2026-10-03T21:56Z as
3222c57404. It has one parent (6946f2f08e) and is an ancestor oforigin/main. - Content check: all 3 files at
3222c57404are blob-equal to the reviewed head4d31b13409(seat-checked, ACCEPT5973440666). No commit between the PR's merge base045b946256and the merge parent touched any of them. - Card: closed
completedby the PR'sFixesline. That was the only closing keyword in the body. This act removespm:dispatchedand the assignee. - What now holds:
release-integrity's audit leavesimage-missingunset, with a notice, while the same version's publishing run has not finished its own image build. That window runs from the publish job's approval until everyDocker imagejob of that run iscompleted, including before GitHub creates the job. An unreadable answer also skips the request, with a warning. One read ofscripts/release-pending-publish.mjs in-flightanswers both backfills. - ⛔ Not a release act: nothing published. The guard first acts on the next release's landings.
- Left in the PR's Acceptance notes, not filed: two backfills racing each other (nothing measured, no run named), and
lint.yml's "holds all three decisions" comment (carrier: the next PR that edits that step).
- Landed: PR fix(release): the image backfill skips a version whose publishing run has not finished building its image #21634 merged through the merge queue at 2026-10-03T21:56Z as
- added a commit that references this issue
on Oct 7, 2026
Filing gate: ① a defect, class (a).
reach:named runs, measured by read-onlyGETon both runs' jobs. Filed by thedomain:specseat 1 (session_01T9u38rswFp5Rw8DswRUReJ, seat post #6017) from the #21359 dev report (PR #21605,out_of_scope_findings[0]), re-measured by the seat before filing. ⛔ Filed bare: grading and routing are triage's. ⛔ Not a claim. ⛔ Release acts stay the maintainer's (AGENTS.md Prime Directive 15).Reader who acts: triage grades and routes it. It is the image position of the family #21313 / #21359 opened (a
release-integritybackfill racing the publishing run). #21359 closes the Releases position (PR #21605), and this card is the family's remaining position.Reach
4e530568a2, run36958423332110687103770"Docker image / Build & push ghcr.io/objectstack-ai/objectstack",successdcc5ef4c5a, run36955885276110687097666, the same job name,successBoth built and pushed the 17.6.0 runtime image at the same time. The tag points at whichever push finished last (03:10:02Z).
Why PR #21605 does not close it
The push lane's audit sets
image-missing=truewhen npm already has the whole fixed group and the image is missing. On 17.6.0, npm settled before the publishing run'sdockerjob ran. That job isneeds: [release-integrity, publish], so it starts only afterpublishcompletes. A guard that reads the publish job alone therefore answers "clear" while the publishing run's image build is still ahead of it or running. PR #21605 leavesimage-missingexactly as it was, by design.Wanted (for triage)
The image backfill should skip a version whose publishing run has not finished its own image build. One shape: extend PR #21605's
in-flightreader (scripts/release-pending-publish.mjs) to the publishing run's docker job, and gateimage-missingon it the same way, failing closed. The cost and shape are the claiming dev's to measure. ⛔ Not a new gate: the guard skips a write and refuses no PR.Dedupe
MCP
search_issues, repo-scoped, open and closed together, "release-integrity image-missing docker image built twice concurrent publish run same version ghcr": 6 hits, none of them this position.github.sha), not the version commit the publish built from #20982 (closed: the Releases backfill's build ref);Dedupe words: release-integrity image-missing concurrent docker build · Docker image duplicate push same version · backfill image publish run in flight · ghcr double build 17.6.0