From cb3f1318db1e933dac3a106a187568550570414f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 3 Aug 2026 14:34:02 +0000 Subject: [PATCH] ci(release): unstick 17.0.0-rc.2 and stop an empty changeset from eating a release (#4898) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 17.0.0-rc.2 is versioned in main (3cfd9f0) and absent from npm. The Release run that should have published it reported success: Create Release Pull Request or Publish to npm ... 0s All changesets are empty; not creating PR changesets/action reaches its publish branch only when there are ZERO pending changesets. An EMPTY changeset still counts as pending, so a main carrying only empty ones takes neither path — no version PR, no publish, published=false, and a green run. `docker` skipped with it, so no runtime image shipped either. Two pending changesets were on main, both empty-frontmatter: #4893's and #4896's. Either one alone is enough. This is not an exotic state — `Check Changeset` sanctions the empty changeset as the "this PR releases nothing" declaration, so any docs/ci PR landing during the version-PR window produces it. Unstick: delete both. An empty-frontmatter changeset declares no releases, so it contributes no entry to any package's CHANGELOG — deleting it discards nothing. Its only job was to satisfy `Check Changeset` for PRs that merged hours ago. With the pending set empty, the next push to main takes the action's normal publish path, which ships the GitHub Releases and the ADR-0087 D4 spec-changes attachment along with npm and Docker. This PR therefore adds no changeset of its own — it carries the `skip-changeset` label instead. Adding an empty one would re-create the exact condition being removed and force the release down the degraded fallback path below. Prevent the silent recurrence: a recovery step after the changesets action, gated on the only fact that matters — the version in the repo is not on the registry. `changeset publish` skips versions already published, so it is a no-op on the normal path (main's version IS the last released one) and a repair on this one. It never succeeds silently: if the version is still missing after publishing, the job fails. The job's `published` / `cli-version` outputs now accept either publish path, so a rescued release still ships its Docker image instead of losing it to the same hole. The fallback cannot create GitHub Releases or the spec-changes attachment — those live inside the action's own publish path — so it says so in a warning rather than leaving the gap to be discovered later. Verified: YAML parses, both job outputs resolve on each path, the step's shell passes `bash -n`, and all three branches were exercised against stubbed npm/pnpm/git — already-published (no-op, exit 0), missing (publishes, sets published/version), and missing-after-publish (::error::, exit 1). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01BbNVKv6KgPzuQ5p76nMgnf --- .../pm-skill-landing-and-ci-discipline.md | 27 ---------- .changeset/release-pr-ci-gates.md | 45 ---------------- .github/workflows/release.yml | 53 ++++++++++++++++++- 3 files changed, 51 insertions(+), 74 deletions(-) delete mode 100644 .changeset/pm-skill-landing-and-ci-discipline.md delete mode 100644 .changeset/release-pr-ci-gates.md diff --git a/.changeset/pm-skill-landing-and-ci-discipline.md b/.changeset/pm-skill-landing-and-ci-discipline.md deleted file mode 100644 index b421fc5465..0000000000 --- a/.changeset/pm-skill-landing-and-ci-discipline.md +++ /dev/null @@ -1,27 +0,0 @@ ---- ---- - -docs(skills): 补齐「跟到合并为止 / CI 诊断纪律 / 生成物同步」四类 PM 经验(#4892) - -发布面零变化 —— 只改 `.claude/skills/` 下的两份内部 agent 说明,不含任何 -workspace 包的代码或产物,故为空 changeset。 - -`pm-dispatch` 的 Operational notes 由四条扩到八条,新增的四条都是 #4885 -(同日另一车道的沉淀)覆盖边界之外的: - -- **5** —— `rerun_failed_jobs` 复用原 run 的提交/合并 ref,不重算。红的原因若是 - 「基上缺一个已合并的修复」,重跑本身无效,只有推新提交才拿得到新的合并 ref。 -- **6** —— 读数纪律:`cd X && cmd` 短路(跨仓一律 `git -C`)、 - `git grep -c | wc -l` 数的是文件数、裸名 grep 被幸存家族当子串命中。 -- **7** —— CI 红了先取完整日志归档:completeness check 绿 ≠ 测试通过、 - turbo 并发输出相邻 ≠ 因果、不要只看 tail。 -- **8** —— 共享基础设施类修复按症状复查 main,并写明 - `duplicate-fix-guard.yml` 只覆盖「同仓 + 同一个 `Fixes #N`」。 - -另在 Operational notes 1 上补了「不在 main 上」是二义读数、队列分支 base sha 串成 -链、转 draft 会同时掉 auto-merge 与队列成员资格;并在 step 7 之后新增「入队与落地」 -小节,写清 `merge=os-regen` 的七条路径与四步同步协议,以及「跟到 MERGED 为止」。 - -`spec-property-retirement` 新增「四张 ratchet 的可见性按路线相反」一节(枚举值收窄 -不可见 vs 整 def 删除必须变化),并修好第 2 节指向 `plugin-runtime.zod.ts:243-248` -的先例引用 —— 那个文件已被 #4878 整体删除。 diff --git a/.changeset/release-pr-ci-gates.md b/.changeset/release-pr-ci-gates.md deleted file mode 100644 index c9740d36c6..0000000000 --- a/.changeset/release-pr-ci-gates.md +++ /dev/null @@ -1,45 +0,0 @@ ---- ---- - -ci(release): unblock the two gates that fail every `chore: version packages` PR (#4894) - -Both failures on #4422 were the gates themselves, not the release PR's content, -and both recur on every release PR. - -**`Check Changeset` was structurally unsatisfiable for the release PR.** The -gate counts changesets a PR *adds* (`git diff --diff-filter=A` against the base) -— the right question for an ordinary PR, and the fix #3373 landed after a global -`find | wc -l` proved unable to ever go red in RC mode. But the Changesets -release PR is the *consuming* side: it applies pending changesets into versions -and CHANGELOGs and adds none, by construction. Nobody labels a bot-authored PR -`skip-changeset`, so the release sat blocked on a check that could only be red. -`changeset-release/main` is now exempt at the job level, pinned to the bot author -as well as the branch name so a hand-pushed branch of that name cannot borrow the -exemption. - -**`Scaffold E2E` skewed the protocol major against itself during an RC window.** -The install step already falls back to `latest` when the repo's version is not -yet published (`@objectstack/cli@^17.0.0-rc.2` → ETARGET → retry as `latest`). -That fallback rewrote the generated project's dependencies but not its manifest, -and the template stamps the repo's protocol major (`engines: { protocol: '^17' }`, -written at version time by `sync-template-versions.mjs`) while `latest` still -pointed at 16.x. The ADR-0087 D1 handshake then correctly refused to boot the -artifact — the gate working, on a skew the step had introduced: - -``` -✗ package 'e2e-app' targets protocol ^17 (engines.protocol) but this runtime is - protocol 16.0.0 -``` - -The fallback now re-stamps `engines.protocol` to the major actually installed, -read off `node_modules/@objectstack/spec` (`PROTOCOL_VERSION` is kept in lockstep -with that package's own major, asserted by `protocol-version.test.ts`), and logs -a `::notice` so the run's true protocol is visible rather than silently rewritten. -It is confined to the fallback branch: on the normal path the project installs -the repo's own version, the majors agree by construction, and a template stamping -the wrong major must still fail — which is what `template-consistency.test.ts` is -for. Re-stamping happens before `npm run build`, so the artifact and the Docker -image (already pinned to the resolved CLI version by the same reasoning) stay in -step. - -CI configuration only; releases nothing. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9d8e027267..38409556e3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,8 +15,11 @@ jobs: contents: write pull-requests: write outputs: - published: ${{ steps.changesets.outputs.published }} - cli-version: ${{ steps.cli-version.outputs.version }} + # Either publish path counts (see the recovery step below) — the Docker + # image must ship for a release the fallback rescued just as much as for + # one the Changesets action published itself. + published: ${{ steps.changesets.outputs.published == 'true' || steps.recover-publish.outputs.published == 'true' }} + cli-version: ${{ steps.cli-version.outputs.version || steps.recover-publish.outputs.version }} steps: - name: Checkout repository uses: actions/checkout@v7 @@ -159,6 +162,52 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish a release the Changesets action left behind + id: recover-publish + if: steps.changesets.outputs.published != 'true' + # changesets/action reaches its publish branch ONLY when there are zero + # pending changesets. An EMPTY changeset still counts as pending, so a + # `main` carrying only empty ones lands in + # All changesets are empty; not creating PR + # and returns: no version PR, no publish, `published=false`, and a GREEN + # run. The version bump is already committed by then, so the release + # simply evaporates — 17.0.0-rc.2 sat versioned-in-repo and absent from + # npm with Release reporting success, and `docker` skipped with it + # (#4898). This is not an exotic state: `Check Changeset` sanctions the + # empty changeset as the "this PR releases nothing" declaration, so any + # ordinary docs/ci PR landing during the version-PR window produces it. + # + # Detect it by the only fact that matters — the version in the repo is + # not on the registry — and repair it. `changeset publish` skips every + # version already published, so this is a no-op on the normal path + # (where main's version IS the last released one) and a repair on this + # one. Never a silent success: if the version is still missing after + # publishing, the job fails. + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + version=$(node -p "require('./packages/cli/package.json').version") + if npm view "@objectstack/cli@$version" version >/dev/null 2>&1; then + echo "@objectstack/cli@$version is already on npm — nothing left to publish." + exit 0 + fi + + echo "::warning::@objectstack/cli@$version is versioned in main but absent from npm and the Changesets action did not publish it (#4898) — publishing it now." + printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_TOKEN" >> "$HOME/.npmrc" + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + pnpm run release + + if ! npm view "@objectstack/cli@$version" version >/dev/null 2>&1; then + echo "::error::publish ran but @objectstack/cli@$version is still not on npm" + exit 1 + fi + { + echo "published=true" + echo "version=$version" + } >> "$GITHUB_OUTPUT" + echo "::warning::Recovered by the fallback path — npm packages and git tags are published, but the GitHub Releases and the ADR-0087 D4 spec-changes attachment were NOT created (those only exist on the Changesets action's own publish path). Create them by hand if this release needs them." + - name: Attach spec-changes.json to the GitHub Release (ADR-0087 D4) # Rebuilds the change manifest with the api-surface diff against the # previously PUBLISHED spec (reusing the ADR-0059 §3 gate artifact) and