From 305e38f5d9cefb30ab541f3fbb4382dade47f4a6 Mon Sep 17 00:00:00 2001 From: Patrick Erichsen Date: Sun, 16 Aug 2026 20:16:40 -0700 Subject: [PATCH] fix(release): allow beta Docker backfills (#124999) --- .../workflows/openclaw-release-publish.yml | 11 ++--- docs/reference/RELEASING.md | 20 +++++++++- .../package-acceptance-workflow.test.ts | 40 +++++++++++++++++++ 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/.github/workflows/openclaw-release-publish.yml b/.github/workflows/openclaw-release-publish.yml index ada112fea843..43453da22484 100644 --- a/.github/workflows/openclaw-release-publish.yml +++ b/.github/workflows/openclaw-release-publish.yml @@ -68,7 +68,7 @@ on: default: true type: boolean publish_docker_only: - description: Publish Docker only after independently verifying an already-published extended-stable npm package + description: Publish Docker only after independently verifying an already-published beta or extended-stable npm package required: true default: false type: boolean @@ -184,8 +184,8 @@ jobs: echo "publish_docker_only requires publish_openclaw_npm=false." >&2 exit 1 fi - if [[ "${RELEASE_NPM_DIST_TAG}" != "extended-stable" ]]; then - echo "publish_docker_only is reserved for an already-published extended-stable release." >&2 + if [[ "${RELEASE_NPM_DIST_TAG}" != "beta" && "${RELEASE_NPM_DIST_TAG}" != "extended-stable" ]]; then + echo "publish_docker_only supports already-published beta or extended-stable releases only." >&2 exit 1 fi fi @@ -2480,14 +2480,15 @@ jobs: - name: Verify exact npm and selector readback matches preflight bytes env: RELEASE_TAG: ${{ inputs.tag }} + RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }} EXPECTED_TARBALL_SHA256: ${{ needs.resolve_release_target.outputs.preflight_tarball_sha256 }} run: | set -euo pipefail version="${RELEASE_TAG#v}" exact_version="$(npm view "openclaw@${version}" version)" - selector_version="$(npm view openclaw@extended-stable version)" + selector_version="$(npm view "openclaw@${RELEASE_NPM_DIST_TAG}" version)" if [[ "${exact_version}" != "${version}" || "${selector_version}" != "${version}" ]]; then - echo "npm exact-version or extended-stable selector readback does not match ${version}." >&2 + echo "npm exact-version or ${RELEASE_NPM_DIST_TAG} selector readback does not match ${version}." >&2 exit 1 fi tarball_url="$(npm view "openclaw@${version}" dist.tarball)" diff --git a/docs/reference/RELEASING.md b/docs/reference/RELEASING.md index b9ba841e0d97..abc9231518ed 100644 --- a/docs/reference/RELEASING.md +++ b/docs/reference/RELEASING.md @@ -565,6 +565,24 @@ gh workflow run openclaw-release-publish.yml \ Include `plugin_sdk_api_acknowledgement` only when the npm preflight's Plugin SDK API report contains changes. +If a beta package is already published but its container images are missing, +do not rerun npm or plugin publication. Reuse the immutable beta tag plus its +successful npm preflight and Full Release Validation evidence through the +Docker-only recovery path. The workflow rechecks the exact npm version, the +`beta` selector, and the published tarball digest before building containers: + +```bash +gh workflow run openclaw-release-publish.yml \ + --ref main \ + -f tag=vYYYY.M.PATCH-beta.N \ + -f preflight_run_id= \ + -f full_release_validation_run_id= \ + -f full_release_validation_run_attempt= \ + -f npm_dist_tag=beta \ + -f publish_openclaw_npm=false \ + -f publish_docker_only=true +``` + Stable publish to the default beta dist-tag: ```bash @@ -681,7 +699,7 @@ readback confirms that every exact package and `extended-stable` tag converged. - `windows_node_installer_digests`: candidate-approved compact JSON map of the current Windows installer names to their pinned `sha256:` digests; required for stable OpenClaw publish - `npm_telegram_run_id`: optional successful `NPM Telegram Beta E2E` run id to include in final release evidence - `npm_dist_tag`: npm target tag for the OpenClaw package, one of `alpha`, `beta`, `latest`, or `extended-stable` -- `publish_docker_only`: extended-stable-only recovery/closeout path. It requires `publish_openclaw_npm=false`, complete preflight and Full Release Validation evidence, then verifies the exact npm package, selector, and tarball digest before invoking Docker publication. +- `publish_docker_only`: beta or extended-stable recovery/closeout path. It requires `publish_openclaw_npm=false`, complete preflight and Full Release Validation evidence, then verifies the exact npm package, selected dist-tag, and tarball digest before invoking Docker publication. - `plugin_publish_scope`: defaults to `all-publishable`; use `selected` only for focused plugin-only repair work with `publish_openclaw_npm=false` - `plugins`: comma-separated `@openclaw/*` package names when `plugin_publish_scope=selected` - `publish_openclaw_npm`: defaults to `true`; set `false` only when using the workflow as a plugin-only repair orchestrator diff --git a/test/scripts/package-acceptance-workflow.test.ts b/test/scripts/package-acceptance-workflow.test.ts index 573e9457e5e6..6e4f29a65f98 100644 --- a/test/scripts/package-acceptance-workflow.test.ts +++ b/test/scripts/package-acceptance-workflow.test.ts @@ -1141,6 +1141,46 @@ describe("package acceptance workflow", () => { ); }); + it("allows Docker-only recovery for beta and extended-stable releases", () => { + for (const release of [ + { distTag: "beta", tag: "v2026.8.1-beta.2" }, + { distTag: "extended-stable", tag: "v2026.7.33" }, + ]) { + const result = runReleasePublishInputValidation({ + PUBLISH_DOCKER_ONLY: "true", + PUBLISH_OPENCLAW_NPM: "false", + RELEASE_NPM_DIST_TAG: release.distTag, + RELEASE_TAG: release.tag, + }); + expect(result.status, result.stderr).toBe(0); + } + + const latest = runReleasePublishInputValidation({ + PUBLISH_DOCKER_ONLY: "true", + PUBLISH_OPENCLAW_NPM: "false", + RELEASE_NPM_DIST_TAG: "latest", + RELEASE_TAG: "v2026.8.1", + }); + expect(latest.status).toBe(1); + expect(latest.stderr).toContain( + "publish_docker_only supports already-published beta or extended-stable releases only", + ); + + const workflow = readWorkflow(RELEASE_PUBLISH_WORKFLOW); + const input = workflow.on?.workflow_dispatch?.inputs?.publish_docker_only as + | { description?: string } + | undefined; + const verifyJob = workflowJob(RELEASE_PUBLISH_WORKFLOW, "verify_core_npm_registry"); + const verifyStep = workflowStep( + verifyJob, + "Verify exact npm and selector readback matches preflight bytes", + ); + expect(input?.description).toContain("beta or extended-stable"); + expect(verifyStep.env?.RELEASE_NPM_DIST_TAG).toBe("${{ inputs.npm_dist_tag }}"); + expect(verifyStep.run).toContain('npm view "openclaw@${RELEASE_NPM_DIST_TAG}" version'); + expect(verifyStep.run).not.toContain("npm view openclaw@extended-stable version"); + }); + it("accepts only main-reachable protected SHA-pinned release publish tags", () => { const workflowSha = "a".repeat(40); const binDir = tempDirs.make("release-publish-gh-");