Uh oh!
There was an error while loading. Please reload this page.
fix(ci): stage what the version pass actually writes in cut-rc.yml - #9553
Merged
os-project-manager merged 1 commit intoAug 18, 2026
Merged
Conversation
`pnpm run version` has ended with `node scripts/sync-docs-image-tags.mjs` since #9064, and `sync-template-versions.mjs` stamps `specVersion` into the blank template's `objectstack.manifest.json`. Neither output was covered by the staging pathspec in "Build the single version commit", so the step's "nothing tracked may be left modified-but-unstaged" assertion tripped and every cut refused to push. Measured, not assumed: a full `pnpm run version` in a throwaway clone over 199 pending changesets (17.0.0 -> 18.0.0-rc.0) leaves 160 dirty paths, four of which fall outside the old allowlist: content/docs/deployment/self-hosting.mdx content/docs/upgrading.mdx docker/README.md packages/create-objectstack/src/templates/blank/objectstack.manifest.json The three doc surfaces are RESOLVED, not restated: `sync-docs-image-tags.mjs` imports `SURFACES` from `scripts/check-docs-image-tag.mjs` rather than owning a list, and this step now joins as a third consumer of that same declaration. Deliberately not a wildcard over `content/docs/**` — a docs page `SURFACES` does not name is still rejected, which is the signal the assertion exists for. Two new guards refuse the cut when the declaration cannot be resolved or resolves empty, so an unknown surface can never degrade into an empty allowlist. The template manifest is named literally beside its sibling `objectstack.config.ts`: `sync-template-versions.mjs` declares its targets but exports none of them and has no entry-point guard, so it cannot be read the way the docs gate can. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HDA9nN6nXQngoQUAAzRdMb
os-project-manager
marked this pull request as ready for review
August 18, 2026 09:33
Uh oh!
There was an error while loading. Please reload this page.
os-project-manager
deleted the
claude/issue-9518-cut-rc-docs-image-tags
branch
August 18, 2026 09:56
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 freeto 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#9518
cut-rc.yml's "Build the single version commit" step stages an allowlisted set ofpaths and then asserts that nothing tracked is left modified-but-unstaged. Since
#9064 put
node scripts/sync-docs-image-tags.mjsat the end ofpnpm run version,that assertion has tripped on every cut: the rewriter's output falls outside the
pathspec, so
LEFTis non-empty and the step exits 1. The rule was right; its inputsurface had gone stale.
Measured, not assumed
Full
pnpm run versionin a throwaway clone (no push remote) oforigin/main@b9f807734, 199 pending changesets,changeset pre enter rcthen the wholepipeline, on today's pinned
@changesets/cli@2.31.1. Result: 160 dirty paths —77
package.json, 76CHANGELOG.md,.changeset/pre.json, and 6 others.Replaying the step's shell verbatim (extracted from the YAML, not hand-copied)
against
origin/main's version reproduces the refusal — and finds four offendingpaths, one more than the card recorded:
The fourth is written by
sync-template-versions.mjs(specVersionto^18.0.0),not by the docs rewriter, so #9518's body does not name it. It is fixed here anyway
because it is the same defect in the same assertion in the same block: widening the
allowlist for only three of the four would leave the cut refusing to push, which is
the acceptance criterion of the card. Per #9465 ruling 3 the allowlist follows the
measurement.
The doc surfaces are read, not restated
sync-docs-image-tags.mjsdoes not own its target list — it importsSURFACESfromscripts/check-docs-image-tag.mjs, the gate whose findings it exists to clear, onthat file's stated principle "one list, two consumers". This step now joins as the
third consumer of the same declaration, resolving it at run time:
node --input-type=module \ -e 'import { SURFACES } from "./scripts/check-docs-image-tag.mjs"; for (const s of SURFACES) console.log(s.file);'That module is import-safe by construction: #9064 added its entry-point guard for
exactly this kind of consumer. Two new guards refuse the cut if the declaration
cannot be resolved, or resolves empty — an unknown surface must never degrade into
an empty allowlist, which would silently re-open this same hole.
The template manifest is instead named literally, beside its sibling
objectstack.config.ts.sync-template-versions.mjsdoes declare its targets (aTEXT_STAMPStable plus a walk ofsrc/templates/) but exports none of them andhas no entry-point guard, so importing it would run the sync rather than read its
list. Giving it those two things would let this pathspec read from it too; filed
separately as an observation.
This is deliberately not a wildcard
content/docs/**would have silenced the exact signal the assertion exists for.Only paths a reviewed declaration names are accepted. Two properties verified by
replay:
content/docspageSURFACESdoes not name is still rejected;grep -vxF(whole-line, fixedstring), so it is exact — an unescaped ERE alternation spelling
README.mdwouldhave accepted
READMEXmd.Verification — five replay legs
The workflow cannot run outside a cut, so its step was extracted from the YAML and
run under
bash -e(the runner's default shell forrun:) against the real dirtytree, restored byte-identically before each leg.
origin/mainSTAGED160,BADempty,LEFTempty, commit builtREADME.mdandcontent/docs/ai/actions-as-tools.mdxSURFACESemptiedLeg 2, instrumented:
157 + 3 = 160: every staged path is accepted by exactly one filter, and nothing is
left behind. Legs 3-5 are the ones that matter most — a gate that can no longer fail
is worse than the bug it was hiding.
No release was performed: throwaway clone, no remotes, no publish, no tags, no
pre-mode entry or exit on
origin, no workflow dispatch.Scope
cut-rc.yml:285's "range is walkable" preflight is untouched — a disjoint blockbelonging to another card. #9498 rewrites this same allowlist for the v3 pre-mode
file set and is currently held on a maintainer decision; per #9518's claim this lands
first and #9498 rebases onto it, which is the handover the filer asked for. That card
is not addressed here.
Gates
Re-derived for the actual diff with
node scripts/pm/dispatch-gates.mjs(same fivefamilies, no new ones) and run green at
0bfd6134b:check:node-version,check:required-contexts,check:shard-attestation,check:workflow-status-functions, pluscheck:nul-bytes.Changeset
None —
skip-changeset. The diff is one workflow file. It publishes no package,changes no user-visible behaviour and ships nothing to consumers; the release-notes
input for a change like this is nil by construction.
Generated by Claude Code