Repository navigation
Conversation
… vacuous backport pass patch-release.js now refuses to tag unless the required unit and integration workflows passed on both release candidates (harper-pro and core), evaluated per job so a narrower re-run cannot mask an earlier failure. --yes aborts with a RESULT line; interactive mode prints the failing checks and makes the proceed prompt an explicit override; --ci-override <reason> covers an emergency release and is recorded. A release cut from its source branch (a main-line release) now reports backport verification as not applicable instead of passing having checked nothing. Each candidate SHA is pinned once after fetch and the release commit is built on it, and prompts settle as "no" on closed stdin instead of exiting silently. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhCspWz6s3qTVR7EsMGqeG Dispatch-Task: harper-pro-patch-release-verify-gates
…se zero-job CI gaps Check out, fast-forward and assert both repositories before core's version bump, so a harper-pro refusal no longer leaves a local core release commit and tag that block the re-run. Block when any run of a required workflow is unfinished or a successful run lists no jobs, and carry the gate fields in the aborted RESULT. Document that closed stdin keeps the branch-restore default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhCspWz6s3qTVR7EsMGqeG Dispatch-Task: harper-pro-patch-release-verify-gates
… results by completion With submodule.recurse set, checking out and fast-forwarding the harper-pro release branch moved core to harper-pro's recorded gitlink after core's base was asserted, so core's release commit landed on a detached HEAD. Run both without recursion and assert core afterwards. A re-run keeps its run_number, so the newest result per job now comes from the job's completed_at. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhCspWz6s3qTVR7EsMGqeG Dispatch-Task: harper-pro-patch-release-verify-gates
… job A job re-run that starts between the runs and jobs queries has no completed_at, so it now outranks every finished result instead of losing to an older green one. Drop the highest-run_number conclusion check, which blocked after an older run's re-run had passed, and instead block any run that ended before a job started until it is re-run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhCspWz6s3qTVR7EsMGqeG Dispatch-Task: harper-pro-patch-release-verify-gates
…workflow Fetch jobs with filter=all so a failure from an earlier attempt that a partial re-run did not repeat stays visible to the per-job recency rule, and block a run that lists no jobs whatever its conclusion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhCspWz6s3qTVR7EsMGqeG Dispatch-Task: harper-pro-patch-release-verify-gates
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhCspWz6s3qTVR7EsMGqeG Dispatch-Task: harper-pro-patch-release-verify-gates
Release cherry-pick
|
There was a problem hiding this comment.
Code Review
This pull request introduces a release-candidate CI gate to the patch-release.js script, ensuring that required CI workflows have passed on the pinned candidate commits before a release is published. It also adds a --ci-override flag for emergency releases, handles cases where backport verification is not applicable, and updates the documentation and test suites. The review feedback highlights a critical cross-platform compatibility issue on Windows where splitting command outputs (from gh api and git diff) solely by \n leaves trailing carriage returns (\r), which can cause JSON parsing failures or malformed filenames. It is recommended to use a regular expression like /\r?\n/ to safely split these lines.
⊙ Problem
scripts/patch-release.jshad two verification gaps, seen during the 5.2.0 release:--branch main, default--source main) checked milestone PRs merged tomainfor presence inmain's own history. Every PR is present by construction, so the script printedBackport verification passed (N PRs)having verified nothing. v5.3.0 and v5.3.1 were cut this way.Release v5.1.29andRelease v5.2.15commits that were tagged have failing unit and integration runs, and the current corev5.3head (ce9621c5) has no runs at all. The cherry-pick workflow advances release branches withGITHUB_TOKEN, and GitHub does not trigger push workflows for those pushes.💡 Solution
Same-ref detection. When the release branch is the source branch (
backportVerificationApplies), the script skips the PR query and printsBackport verification not applicable: release cut from source branch main; no backport evidence was checked.It never prints the pass line in that case.RESULT.backportVerificationreportspassed,missingornot-applicableper repository, so a--jsoncaller cannot read a vacuous pass either.Release-candidate CI gate. The release candidate is the fetched⚠️ Look hardest:
origin/<branch>commit that the release commit is built on. It is pinned once per repository, and the backport check, version read, CI query and release-commit parent all use that SHA. For each required workflow (REQUIRED_WORKFLOWS: coreunit-test.ymlandintegration-tests.yml; harper-prounit-tests.yamlandintegration-tests.yaml, the workflows that pushes to bothmainandvX.Ytrigger), the script readsactions/workflows/<file>/runs?head_sha=<sha>and every attempt's jobs for each run (filter=all). InevaluateWorkflowRuns, each job's result comes from its most recent completion (a re-run keeps itsrun_number, so recency comes from the job'scompleted_at; an unfinished job counts as newest).pull_requestruns are ignored because they test a merge ref. >evaluateWorkflowRunsis the definition of green.These all block: a missing or unfinished run, a run that lists no jobs, a failed or cancelled job, and an API error.
--yesaborts before any checkout, version bump, tag or push, including under--dry-run. It exits 1 and emitsRESULTeven without--json, withciFailures(repo, branch, sha, workflow, job, state, url or error) next tomissingPRs.ciOverride: { reason: 'interactive confirmation' }.--ci-override "<reason>"is the emergency path in either mode. The reason is printed and recorded inRESULT.ciOverride. It does not waive missing backports, and a blank reason is rejected.Tagged tree = verified tree. Both repositories are checked out at their pinned candidates, and
assertReleaseBaseruns on each before either gets a release commit. It refuses a HEAD other than the candidate, any staged change, and a modifiedpackage.jsonorpackage-lock.json. Harper-pro is checked out with-c submodule.recurse=false. Withsubmodule.recurseset, that checkout moves core to harper-pro's recorded gitlink. Onmainit ran after core's release commit, so harper-pro could pin the old core. Under the first version of this PR's ordering, core's release commit landed on a detached HEAD instead. Review caught it.Prompts on closed stdin.
prompt()used one readline interface per prompt. Measured: an interface created after stdin ended never settled, so the process exited 0 without aRESULT, and an earlier interface swallowed piped answers meant for later prompts. One shared reader, paused between prompts, now returnsnullon closed stdin. The proceed and CM-deploy prompts treatnullas "no"; branch restore keeps its default.⚖️ Alternatives
mainandvX.Ytrigger. Lint, typecheck and format run onmainpushes only, so requiring them would block every release-branch candidate. To widen the set, editREQUIRED_WORKFLOWS.successconclusion. No real run of these workflows succeeds without jobs, so this only fires on an API anomaly;--ci-overridegets past it.if:). Treating skipped as failing would block on those legitimate skips. The trade-off is that a test job skipped by a futureif:would also read as green.mainheads, mostlyCompanion Check), and it cannot filter outpull_requestruns.rules/branches/v5.3has no rules in either repository.main's required checks are PR merge gates (companion-check,validate / validate) that never run on a push commit.--branchequal to--source. Main-line releases are a real flow (v5.3.0, v5.3.1).🔧 Changes
scripts/patch-release.js: everything above, plus version reads from the pinned candidates, the gate fields in the success and abortedRESULT(andbuildAbortedResult(gate)), and the missing-run dispatch hint. Look hardest atevaluateWorkflowRuns, the candidate pin, and the reordered Step 1.CONTRIBUTING.md: the new procedure step, a Release-candidate CI section (evidence rules, how to produce missing runs, theRESULTcontract, the pairing limit, the Actions-read token requirement), the--ci-overriderow, and theRESULTfields and closed-stdin note.scripts/DESIGN.mdandDESIGN.md: the same-ref invariant, plus a new Release-candidate CI gate note and its index entry.AGENTS.md: thescripts/map line now mentions the CI check.✅ Verification
Route: spawned-CLI tests against real git fixture repositories with a scripted
ghshim, plus live dry-runs against GitHub. The release runs on a maintainer's machine, not inside the server, so integration tests do not exercise it.npx mocha --require unitTests/unitTestSetup.cjs 'unitTests/scripts/*.test.mjs': 99 passing.unitTests/scripts/patchReleasePresence.test.mjs, unit level:backportVerificationApplies, andevaluateWorkflowRunsover runs/jobs fixtures shaped like the REST rows (green, missing, pull_request-only, failed or cancelled jobs, unfinished runs, a job re-run that started after the runs were listed, flake superseded, newer failure, a re-run of an older run superseding or not superseding a newer one, an earlier attempt's unrepeated failure,run_numberordering, narrow-run masking, job-less runs including beside a green run,startup_failure, malformedrun_number).ghshim gains Actions routes and unsigned tags):--yes(no--json) aborts on a failed core or pro check with local and remote refs, tags andpackage.jsonunchanged, and the queriedhead_shais the pinned candidate. Also covered: dry-run abort on an API error, the dispatch command for a missing run,--ci-overridereason recorded, the override not waiving a missing backport, a blank reason rejected, interactive decline and closed-stdin abort carrying the gate fields, interactive "y" recorded with CM deploy declined on closed stdin, the tag landing on the pinned candidate whenorigin/v5.1is moved during the CI query, a staged change refused before any core or pro tag exists, core's release commit built on its candidate undersubmodule.recurse=true, the pairing warning, and--branch mainprinting not-applicable with no PR query (decided per repository with--core-branch).unitTests/scripts/patchRelease.test.mjs: thebuildSuccessResultgate fields and a recorded override.Fails on base. Against
origin/main's script, 38 of the 99 tests fail. Three targeted mutations prove the mechanism tests. Reverting the merge toorigin/<branch>makes the tag land on the unverified commit. Asserting harper-pro after core's bump leaves a corev5.1.1tag behind. A recursive harper-pro checkout leaves core's release commit on a detached HEAD.npm run build && npm run test:unit: 1601 passing, 2 pending.Live dry-run (
--dry-run --yes --json, from a fresh clone, against GitHub):v5.3main47984013, merged 23 min earlier)v5.2filter=allvsfilter=latest, measured on two real attempt-2 runs:latestlists only attempt 2's jobs (27/27 and 32/32 carried forward), whileallreturns both attempts, including attempt 1's failure.A shared paused reader does not hold an interactive TTY open. On a pty with stdin held open for 20 s, node exited 0.17 s after start.
prettier --checkandoxlint --deny-warningsare clean on the changed files. The review CLI'sno-restart-testself-check does not apply: this script keeps no crash-recovery state, and a run killed partway leaves the same state as onmain.🤖 Generated by Anthropic Claude (Opus 5.5) via Claude Code dev-agent; posted via @kriszyp.
Related PRs: #970 overlaps (semver preflight in the same script, CONTRIBUTING.md and contract tests; independent change, at most adjacent-hunk conflicts)
Complexity: medium
Review-Coverage: authored=claude; ran=cursor-composer,gemini,codex; adjudicated=domain; blocked=claude(fallback)(out-of-budget),cursor-grok(failed); declined=cursor-kimi,cursor-muse; rounds=6; full=2 @ 9d051e9
Review-Attention: read ~6m (decisions: do-less-alternative, recency-by-completion, every-empty-run-blocks, observed-job-set-is-green, gate-parent-not-release-commit, gitlink-mismatch-warns, interactive-override-reasonless) @ 9d051e9