diff --git a/.github/workflows/plugin-clawhub-new.yml b/.github/workflows/plugin-clawhub-new.yml index 8901e5ad40b5..600300334cc3 100644 --- a/.github/workflows/plugin-clawhub-new.yml +++ b/.github/workflows/plugin-clawhub-new.yml @@ -344,6 +344,7 @@ jobs: - name: Validate release publish approval run env: + ALLOW_COMPLETED_SUCCESSFUL_PARENT: "true" APPROVAL_PATH: ${{ runner.temp }}/clawhub-bootstrap-approval/approval.json CHILD_WORKFLOW_SHA: ${{ github.sha }} GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/plugin-clawhub-release.yml b/.github/workflows/plugin-clawhub-release.yml index 13f5dd21f171..e0e039faaf55 100644 --- a/.github/workflows/plugin-clawhub-release.yml +++ b/.github/workflows/plugin-clawhub-release.yml @@ -281,6 +281,7 @@ jobs: - name: Validate release publish approval run env: + ALLOW_COMPLETED_SUCCESSFUL_PARENT: "true" GH_TOKEN: ${{ github.token }} RELEASE_PUBLISH_RUN_ID: ${{ inputs.release_publish_run_id }} EXPECTED_WORKFLOW_BRANCH: ${{ inputs.release_publish_branch || github.ref_name }} diff --git a/scripts/validate-release-publish-approval.mjs b/scripts/validate-release-publish-approval.mjs index dd0ceeef51e0..96f96833290a 100644 --- a/scripts/validate-release-publish-approval.mjs +++ b/scripts/validate-release-publish-approval.mjs @@ -7,6 +7,7 @@ const run = JSON.parse(fs.readFileSync(0, "utf8")); const releasePublishRunId = process.env.RELEASE_PUBLISH_RUN_ID ?? ""; const expectedBranch = process.env.EXPECTED_WORKFLOW_BRANCH ?? ""; const directRecovery = process.env.DIRECT_RELEASE_RECOVERY === "true"; +const allowCompletedSuccessfulParent = process.env.ALLOW_COMPLETED_SUCCESSFUL_PARENT === "true"; const approvalPath = process.env.APPROVAL_PATH ?? ""; const approvalKind = process.env.RELEASE_APPROVAL_KIND ?? "android"; const expectedRunAttempt = process.env.EXPECTED_RUN_ATTEMPT ?? ""; @@ -107,6 +108,20 @@ if (expectedRunAttempt && run.runAttempt !== positiveRunAttempt(expectedRunAttem } if (!directRecovery) { + if (run.status === "in_progress" && !run.conclusion) { + console.log(`Using release publish approval run ${releasePublishRunId}: ${run.url}`); + process.exit(0); + } + if ( + allowCompletedSuccessfulParent && + run.status === "completed" && + run.conclusion === "success" + ) { + console.log( + `Using successful completed release publish run ${releasePublishRunId}: ${run.url}`, + ); + process.exit(0); + } if (run.status !== "in_progress") { fail( `Referenced release publish run ${releasePublishRunId} must still be in_progress, got ${run.status ?? ""}.`, @@ -117,8 +132,6 @@ if (!directRecovery) { `Referenced release publish run ${releasePublishRunId} already concluded ${run.conclusion}.`, ); } - console.log(`Using release publish approval run ${releasePublishRunId}: ${run.url}`); - process.exit(0); } if (run.status === "in_progress" && !run.conclusion) { diff --git a/test/scripts/validate-release-publish-approval.test.ts b/test/scripts/validate-release-publish-approval.test.ts index ef4f60da8b53..b2f643e5488c 100644 --- a/test/scripts/validate-release-publish-approval.test.ts +++ b/test/scripts/validate-release-publish-approval.test.ts @@ -11,6 +11,7 @@ const tempRoots = useAutoCleanupTempDirTracker(afterEach); function runApprovalScript( run: Record, env: { + ALLOW_COMPLETED_SUCCESSFUL_PARENT?: string; CHILD_WORKFLOW_SHA?: string; DIRECT_RELEASE_RECOVERY?: string; EXPECTED_WORKFLOW_BRANCH?: string; @@ -29,6 +30,7 @@ function runApprovalScript( encoding: "utf8", env: { ...process.env, + ALLOW_COMPLETED_SUCCESSFUL_PARENT: env.ALLOW_COMPLETED_SUCCESSFUL_PARENT ?? "false", CHILD_WORKFLOW_SHA: env.CHILD_WORKFLOW_SHA ?? "b".repeat(40), DIRECT_RELEASE_RECOVERY: env.DIRECT_RELEASE_RECOVERY ?? "false", EXPECTED_WORKFLOW_BRANCH: env.EXPECTED_WORKFLOW_BRANCH ?? "release/2026.6.21", @@ -131,6 +133,29 @@ describe("scripts/validate-release-publish-approval.mjs", () => { expect(result.stdout).toBe(""); }); + it("accepts a successful completed parent for detached publication", () => { + const result = runApprovalScript(approvalRun({ conclusion: "success", status: "completed" }), { + ALLOW_COMPLETED_SUCCESSFUL_PARENT: "true", + }); + + expect(result.status).toBe(0); + expect(result.stdout).toContain( + "Using successful completed release publish run 123: https://github.com/openclaw/openclaw/actions/runs/123", + ); + expect(result.stderr).toBe(""); + }); + + it("rejects a failed completed parent for detached publication", () => { + const result = runApprovalScript(approvalRun({ conclusion: "failure", status: "completed" }), { + ALLOW_COMPLETED_SUCCESSFUL_PARENT: "true", + }); + + expect(result.status).toBe(1); + expect(result.stderr).toContain( + "Referenced release publish run 123 must still be in_progress, got completed.", + ); + }); + it("accepts an exact attested Android release approval", () => { const approvalPath = writeApproval();