Release automation - #6352
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe release process now validates versions, creates release tags, promotes and signs Phar artifacts, creates draft releases, verifies published assets, updates development repositories, closes milestones, and dispatches website notifications. The checklist documents the automated flow and remaining manual tasks. ChangesRelease automation
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Maintainer
participant PrepareRelease
participant Bundle as wp-cli/wp-cli-bundle
participant WPCLI as wp-cli/wp-cli
participant ReleaseWorkflow
participant Builds as wp-cli/builds
participant Releases as GitHub Releases
Maintainer->>PrepareRelease: Start stable release
PrepareRelease->>Bundle: Create bundle tag
PrepareRelease->>WPCLI: Create main repository tag
WPCLI->>ReleaseWorkflow: Trigger tagged release
ReleaseWorkflow->>Builds: Promote, checksum, and sign artifacts
ReleaseWorkflow->>Releases: Create draft releases with assets
sequenceDiagram
participant Release as Published release
participant PostReleaseWorkflow
participant WPCLI as wp-cli/wp-cli
participant Bundle as wp-cli/wp-cli-bundle
participant WPDev as wp-cli/wp-cli-dev
participant Website as wp-cli.github.com
Release->>PostReleaseWorkflow: Trigger stable release workflow
PostReleaseWorkflow->>WPCLI: Verify assets and update development version
PostReleaseWorkflow->>Bundle: Reset dependency and alias
PostReleaseWorkflow->>WPDev: Close released milestones
PostReleaseWorkflow->>Website: Dispatch release notification
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/ISSUE_TEMPLATE/4-REGULAR_RELEASE_CHECKLIST.md:
- Around line 94-102: Update
.github/ISSUE_TEMPLATE/4-REGULAR_RELEASE_CHECKLIST.md lines 94-102 to instruct
pushing the matching v2.x.0 tag to wp-cli/wp-cli-bundle before creating its
draft release. Update both gh release create commands in
.github/workflows/release.yml lines 125-130 and 139-144 to include --verify-tag,
ensuring releases fail instead of synthesizing or accepting an incorrect tag.
In @.github/workflows/post-release.yml:
- Around line 75-85: Update the “Reset framework constraint and branch-alias”
workflow step so Composer refreshes composer.lock after changing composer.json,
then stage and commit both files before pushing. Preserve the existing dev-main
requirement and branch-alias update while ensuring the lockfile matches them.
- Around line 25-37: Add a validation step in the workflow before any changes
are pushed to main that reads wp-cli/VERSION and compares it with VERSION
derived from TAG; if they differ, fail the job and skip subsequent milestone or
branch updates. Keep the existing next-version calculations and outputs
unchanged for matching versions.
In @.github/workflows/release.yml:
- Around line 107-116: Update the stable-build commit/push step to detect
whether staged changes exist and skip commit and push when there is no diff. In
the draft release steps, replace create-only commands with `gh release edit` for
existing drafts and `gh release upload --clobber` for assets, preserving
rerun-safe behavior when the tag already exists.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 4d091471-292f-4a64-a612-e44c9482c214
📒 Files selected for processing (3)
.github/ISSUE_TEMPLATE/4-REGULAR_RELEASE_CHECKLIST.md.github/workflows/post-release.yml.github/workflows/release.yml
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/post-release.yml:
- Around line 48-58: Update the version validation step around
CURRENT_BASE_VERSION to also accept the calculated NEXT_ALPHA value as a valid
already-bumped state, allowing retries after the later push while continuing to
reject versions unrelated to RELEASED_VERSION. Preserve the existing mismatch
error and failure behavior for all other values.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ca03b773-50a0-4233-9372-7a8f0e3b375f
📒 Files selected for processing (3)
.github/ISSUE_TEMPLATE/4-REGULAR_RELEASE_CHECKLIST.md.github/workflows/post-release.yml.github/workflows/release.yml
🚧 Files skipped from review as they are similar to previous changes (2)
- .github/ISSUE_TEMPLATE/4-REGULAR_RELEASE_CHECKLIST.md
- .github/workflows/release.yml
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces GitHub Actions-based automation for WP-CLI’s release and post-release processes, and updates the regular release checklist to align with the new workflow-driven steps.
Changes:
- Added a
Releaseworkflow to generate release notes, promote/sign stable PHAR artifacts, and create/update draft releases forwp-cli/wp-cliandwp-cli/wp-cli-bundle. - Added a
Post-Release Automationworkflow to bump versions/branch-aliases, reset bundle dependencies, close milestones, and dispatch a website notification after a release is published. - Simplified the regular release checklist to reflect the new automated steps and remaining manual tasks.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| .github/workflows/release.yml | New workflow to promote/sign artifacts and create/update draft GitHub releases. |
| .github/workflows/post-release.yml | New workflow to automate post-release version bumps, dependency reset, milestone closure, and website dispatch. |
| .github/ISSUE_TEMPLATE/4-REGULAR_RELEASE_CHECKLIST.md | Updated checklist to match the new automated release flow and remaining manual steps. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Tagging by hand meant pushing two tags in the right order: the wp-cli tag triggers the release workflow, which needs the wp-cli-bundle tag to already exist before it can attach a draft release to it. Nothing enforced that. This workflow tags the bundle first and wp-cli second, and gates both on the checks that were previously spread across the checklist: - the VERSION file on main matches the version being released - wp-cli-bundle has the framework constraint locked to that version - wp-cli-release.phar in the builds repo actually reports that version - neither tag exists yet Tags are pushed with ACTIONS_BOT rather than GITHUB_TOKEN, because pushes made with GITHUB_TOKEN do not trigger further workflow runs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xw2XwqE3C4cGGZ6EbS8p3V
Correctness fixes: - post-release ran `wp maintenance close-released`, which is not a registered command. wp-cli-dev's bootstrap registers Release_Command as `maintenance release`, with `close-released` as a subcommand of that, so the milestones were never going to close. - post-release triggered on `release: [published]`, which also fires for pre-releases. Publishing something like v3.0.0-beta.1 would have bumped versions, reset the bundle constraint and closed milestones. It now uses `released`, matching trigger-handbook-regeneration.yml and check-branch-alias.yml, and rejects non-stable versions outright. - The version bump assumed every release is a minor bump and failed the whole run when it was not. A patch release cut from a maintenance branch, or a re-run after the bump already happened, now skips the bump with a notice and still closes milestones and dispatches to the website. - post-release pushed to wp-cli/wp-cli main with GITHUB_TOKEN. main is branch-protected, and pushes made with GITHUB_TOKEN do not trigger downstream workflows such as check-branch-alias. Both repos now use ACTIONS_BOT. Safety: - The stable Phar was promoted, signed and published without ever checking what was in it. wp-cli-release.phar is produced by another workflow in another repository, so nothing tied it to the tag being released; a stale or failed bundle build would have been signed and shipped as stable. The workflow now refuses to continue unless the Phar reports the version being released, and runs the `--info` sanity check the checklist used to ask for by hand. - Signatures and checksums are verified after they are generated, so a mis-imported key or an empty hash file fails the run instead of being published. - Both workflows now name bash explicitly, which enables `-o pipefail`. Without it a failing `sha512sum` in `sha512sum ... | cut ...` produced an empty checksum file that was then signed and published. - Release notes are written to separate files and asserted non-empty before use. `contrib-list` and `release-notes` read from the currently open milestone, so re-running after post-release closes them would otherwise overwrite good release notes with nothing. They are also uploaded as an artifact so they survive a later failure. - Both workflows have a concurrency group, and the builds push retries on top of concurrent nightly pushes rather than failing. - Restores the `deb/php-wpcli_latest_all.deb` symlink update, which the checklist dropped without automating. Adds a verify-release job that post-release depends on. It downloads the published assets and checks the checksums, the GPG signatures, that the Phar runs and reports the right version, that the manifest is valid JSON, and that the release asset is byte-identical to the stable build in wp-cli/builds. None of the version bumps or milestone closures run unless that passes. Both workflows also take a dry_run input that runs everything except the pushes, releases and dispatch, printing what would have changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xw2XwqE3C4cGGZ6EbS8p3V
Tagging is now done by the Prepare Release workflow rather than by hand, so the manual tag steps are replaced by running that workflow (with a dry run first). Artifact verification is now automated in post-release, so the manual verification step is narrowed to the upgrade path that automation cannot reach. Also corrects the claim that post-release triggers handbook regeneration -- that runs from trigger-handbook-regeneration.yml. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xw2XwqE3C4cGGZ6EbS8p3V
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
.github/ISSUE_TEMPLATE/4-REGULAR_RELEASE_CHECKLIST.md (1)
139-146: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winRemoving the Debian verification step relies on a workflow step that can silently skip.
The automated Debian symlink update in
.github/workflows/release.ymlLines 227-240 emits a::warning::and exits 0 whenphp-wpcli_${VERSION}_all.debis absent. No manual step now catches that case, sophp-wpcli_latest_all.debcan stay pointed at the previous release without anyone noticing.Either restore a short manual verification item here, or make the release workflow fail on a missing
.deb. See the related comment on.github/workflows/release.ymlLines 227-240.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/ISSUE_TEMPLATE/4-REGULAR_RELEASE_CHECKLIST.md around lines 139 - 146, Restore a concise manual checklist item under “Post-Release Manual Tasks” to verify that the Debian symlink points to the newly released php-wpcli_${VERSION}_all.deb, covering the case where the automated release workflow skips the update because the package is missing..github/workflows/post-release.yml (1)
268-289: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winUse the bundle’s own state to gate the bundle reset.
After the first run pushes the
wp-cli/wp-cliversion bump, reruns computeshould_bump=falseand skip thewp-cli/wp-cli-bundlereset. If the first run exited before that step, the bundle remains pinned to the released framework on later reruns. The stagedgit diff --quietcheck already makes this step safe when no bundle changes are needed.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/post-release.yml around lines 268 - 289, Update the condition on the “Reset framework constraint, branch-alias and lockfile in wp-cli/wp-cli-bundle” step to use the bundle repository’s own pending-change state rather than steps.vars.outputs.should_bump. Preserve the existing staged-diff safety check and commit/push behavior so reruns can reset an unfinished bundle without creating unnecessary commits.
🧹 Nitpick comments (5)
.github/workflows/post-release.yml (1)
26-27: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winReduce the workflow token to
contents: read.Every write in this workflow authenticates with
secrets.ACTIONS_BOT: the checkouts at Lines 187-195, 260-266 and 291-295, the milestone step at Line 299, and the dispatch at Line 320. The onlyGITHUB_TOKENuse is the fallback forgh release downloadat Line 80, which needs read access only.
contents: writeon the default token therefore grants more than the workflow uses.🛡️ Proposed change
permissions: - contents: write + contents: read🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/post-release.yml around lines 26 - 27, Change the workflow-level permissions block in post-release.yml from contents: write to contents: read. Keep the existing ACTIONS_BOT-authenticated write operations and the GITHUB_TOKEN fallback for gh release download unchanged.Source: Linters/SAST tools
.github/workflows/release.yml (2)
50-65: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueNon-stable tag pushes will produce a failed workflow run.
This workflow triggers on tag pushes. If someone pushes a pre-release tag such as
v3.0.0-beta.1, the regex rejects it and the job exits 1. That marks a red failure on a legitimate action rather than a skip.Consider filtering the trigger with a tag pattern, or exiting 0 with a
::notice::for non-stable tags and gating later steps on an output. Keepexit 1for theworkflow_dispatchpath, where an invalid input is an operator error.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/release.yml around lines 50 - 65, Update the Determine version step to distinguish tag-push events from workflow_dispatch using the existing REF_NAME or event context: for non-stable pushed tags, emit a notice and exit successfully while setting an output that prevents later release steps from running; retain exit 1 for invalid workflow_dispatch inputs. Ensure stable tags continue through the existing tag and version outputs.
120-128: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winThe org-wide
composer installis unbounded in both workflows. Thewp-cli-devpre-install-cmdhook clones every repository in the WP-CLI organization. Neither step setstimeout-minutes, so a throttled or stalled clone runs to the default six-hour job limit while holding acancel-in-progress: falseconcurrency group. That blocks all later release runs.
.github/workflows/release.yml#L120-L128: addtimeout-minutesto the "Install dependencies for maintenance tools" step..github/workflows/post-release.yml#L297-L315: addtimeout-minutesto the "Close released milestones across bundle repos" step, and move theDRY_RUNcheck above thecomposer installso a dry run does not pay the clone cost.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/release.yml around lines 120 - 128, Bound the composer install steps to prevent stalled organization-wide clones from occupying the release concurrency group: in .github/workflows/release.yml lines 120-128, add a suitable timeout-minutes value to the “Install dependencies for maintenance tools” step; in .github/workflows/post-release.yml lines 297-315, add timeout-minutes to the “Close released milestones across bundle repos” step and move its DRY_RUN check before composer install so dry runs skip dependency installation..github/workflows/prepare-release.yml (2)
79-84: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winRead-only checkouts persist the default token. Both workflows check out
wp-cli/buildsfor inspection only, and neither setspersist-credentials: false.actions/checkouttherefore writes theGITHUB_TOKENinto the checked-out.git/config, where any later step or uploaded artifact can read it. zizmor reports this asartipackedat both sites.
.github/workflows/prepare-release.yml#L79-L84: addpersist-credentials: falseto thewp-cli/buildscheckout. Leave thewp-cliandwp-cli-bundlecheckouts at Lines 64-77 unchanged, because their persisted credentials serve the latergit pushsteps..github/workflows/post-release.yml#L150-L159: addpersist-credentials: falseto thewp-cli/buildscheckout, and to thewp-cli-devcheckout at Lines 291-295. Leave thewp-cliandwp-cli-bundlecheckouts unchanged, because they push withsecrets.ACTIONS_BOT.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/prepare-release.yml around lines 79 - 84, Disable credential persistence on the read-only wp-cli/builds checkout in .github/workflows/prepare-release.yml lines 79-84. Also disable it on the wp-cli/builds checkout in .github/workflows/post-release.yml lines 150-159 and the wp-cli-dev checkout in .github/workflows/post-release.yml lines 291-295; leave the wp-cli and wp-cli-bundle checkouts unchanged because their credentials are required for later pushes.Source: Linters/SAST tools
156-186: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winA partial tag push leaves the release in a state that blocks reruns.
The bundle tag is pushed first. If the second push to
wp-cli/wp-clifails, the bundle tag stays in place. A rerun then fails at the "Verify the tags do not already exist" step, because that step treats any existing tag as fatal. Recovery requires manual tag deletion inwp-cli/wp-cli-bundle.Two options:
- Make the existence check tolerant: allow an existing tag when it already points at the expected commit, and fail only on a mismatch.
- Document the manual recovery step in the release checklist so an operator knows to delete the orphaned bundle tag.
Do you want me to draft the tolerant-check version?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/prepare-release.yml around lines 156 - 186, Update the release tag verification step to allow an existing tag when it already resolves to the expected commit, while failing on missing or mismatched tags. Apply this consistently to both wp-cli-bundle and wp-cli so reruns after a partial push can proceed without manual tag deletion; keep the existing fatal behavior for conflicting tags.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/post-release.yml:
- Around line 116-148: Reorder the workflow so the “Import GPG key” and “Verify
the published GPG signatures” steps run before “Verify the published Phar runs
and reports the right version,” ensuring no Phar command executes before
signature verification. Update the import step to use a GPG_PUBLIC_KEY secret or
another public-key source, remove the private-key and passphrase inputs, and
retain the existing gpg verification commands.
In @.github/workflows/prepare-release.yml:
- Around line 141-154: Update the “Verify the tags do not already exist” step to
capture the exit status of git ls-remote separately from the grep emptiness
check for each repository. Treat any nonzero git ls-remote status as a
validation failure and exit nonzero before release tag creation, while
preserving the existing FOUND=1 behavior when an existing tag is detected.
In @.github/workflows/release.yml:
- Around line 225-240: Update the “Point the latest Debian package” step to fail
when the expected DEB_FILE is missing instead of emitting a warning and exiting
successfully. If package creation is asynchronous, add polling consistent with
the existing bundle-tag polling flow, then terminate with a failure after the
timeout; only create the php-wpcli_latest_all.deb symlink once the package
exists.
---
Outside diff comments:
In @.github/ISSUE_TEMPLATE/4-REGULAR_RELEASE_CHECKLIST.md:
- Around line 139-146: Restore a concise manual checklist item under
“Post-Release Manual Tasks” to verify that the Debian symlink points to the
newly released php-wpcli_${VERSION}_all.deb, covering the case where the
automated release workflow skips the update because the package is missing.
In @.github/workflows/post-release.yml:
- Around line 268-289: Update the condition on the “Reset framework constraint,
branch-alias and lockfile in wp-cli/wp-cli-bundle” step to use the bundle
repository’s own pending-change state rather than
steps.vars.outputs.should_bump. Preserve the existing staged-diff safety check
and commit/push behavior so reruns can reset an unfinished bundle without
creating unnecessary commits.
---
Nitpick comments:
In @.github/workflows/post-release.yml:
- Around line 26-27: Change the workflow-level permissions block in
post-release.yml from contents: write to contents: read. Keep the existing
ACTIONS_BOT-authenticated write operations and the GITHUB_TOKEN fallback for gh
release download unchanged.
In @.github/workflows/prepare-release.yml:
- Around line 79-84: Disable credential persistence on the read-only
wp-cli/builds checkout in .github/workflows/prepare-release.yml lines 79-84.
Also disable it on the wp-cli/builds checkout in
.github/workflows/post-release.yml lines 150-159 and the wp-cli-dev checkout in
.github/workflows/post-release.yml lines 291-295; leave the wp-cli and
wp-cli-bundle checkouts unchanged because their credentials are required for
later pushes.
- Around line 156-186: Update the release tag verification step to allow an
existing tag when it already resolves to the expected commit, while failing on
missing or mismatched tags. Apply this consistently to both wp-cli-bundle and
wp-cli so reruns after a partial push can proceed without manual tag deletion;
keep the existing fatal behavior for conflicting tags.
In @.github/workflows/release.yml:
- Around line 50-65: Update the Determine version step to distinguish tag-push
events from workflow_dispatch using the existing REF_NAME or event context: for
non-stable pushed tags, emit a notice and exit successfully while setting an
output that prevents later release steps from running; retain exit 1 for invalid
workflow_dispatch inputs. Ensure stable tags continue through the existing tag
and version outputs.
- Around line 120-128: Bound the composer install steps to prevent stalled
organization-wide clones from occupying the release concurrency group: in
.github/workflows/release.yml lines 120-128, add a suitable timeout-minutes
value to the “Install dependencies for maintenance tools” step; in
.github/workflows/post-release.yml lines 297-315, add timeout-minutes to the
“Close released milestones across bundle repos” step and move its DRY_RUN check
before composer install so dry runs skip dependency installation.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 672436bb-7f10-4538-a664-4e01d6ebb0d2
📒 Files selected for processing (4)
.github/ISSUE_TEMPLATE/4-REGULAR_RELEASE_CHECKLIST.md.github/workflows/post-release.yml.github/workflows/prepare-release.yml.github/workflows/release.yml
| - name: Verify the published Phar runs and reports the right version | ||
| env: | ||
| VERSION: ${{ steps.vars.outputs.version }} | ||
| run: | | ||
| cd verify | ||
|
|
||
| PHAR_VERSION="$(php "wp-cli-${VERSION}.phar" --version | awk '{print $2}')" | ||
|
|
||
| if [ "$PHAR_VERSION" != "$VERSION" ]; then | ||
| echo "::error::Published Phar reports '$PHAR_VERSION' but the release is '$VERSION'." | ||
| exit 1 | ||
| fi | ||
|
|
||
| php "wp-cli-${VERSION}.phar" --info | ||
|
|
||
| if ! jq -e . "wp-cli-${VERSION}.manifest.json" > /dev/null; then | ||
| echo "::error::wp-cli-${VERSION}.manifest.json is not valid JSON." | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Import GPG key | ||
| uses: crazy-max/ghaction-import-gpg@cb9bde2e2525e6865904e392450c3401a753db76 # v6.2.0 | ||
| with: | ||
| gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} | ||
| passphrase: ${{ secrets.GPG_PASSPHRASE }} | ||
|
|
||
| - name: Verify the published GPG signatures | ||
| env: | ||
| VERSION: ${{ steps.vars.outputs.version }} | ||
| run: | | ||
| cd verify | ||
| gpg --verify "wp-cli-${VERSION}.phar.asc" "wp-cli-${VERSION}.phar" | ||
| gpg --verify "wp-cli-${VERSION}.phar.gpg" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Verify the GPG signature before you execute the downloaded Phar, and import only the public key.
Two ordering and scope problems:
-
Lines 122 and 129 run the downloaded artifact with
php. The signature check that establishes its authenticity does not run until Lines 142-148. The workflow therefore executes the artifact before it trusts it. Move the "Import GPG key" and "Verify the published GPG signatures" steps ahead of "Verify the published Phar runs and reports the right version". -
crazy-max/ghaction-import-gpgimports the private signing key with its passphrase. Verification needs only the public key. This job never signs anything, so the private key andGPG_PASSPHRASEare exposed to a runner for no functional reason. Import the public key instead.
The combination matters: the runner executes an unverified binary and then holds the release signing key and ACTIONS_BOT.
🛡️ Proposed restructuring
+ - name: Import the release public key
+ env:
+ GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }}
+ run: |
+ printf '%s' "$GPG_PUBLIC_KEY" | gpg --batch --import
+
+ - name: Verify the published GPG signatures
+ env:
+ VERSION: ${{ steps.vars.outputs.version }}
+ run: |
+ cd verify
+ gpg --verify "wp-cli-${VERSION}.phar.asc" "wp-cli-${VERSION}.phar"
+ gpg --verify "wp-cli-${VERSION}.phar.gpg"
+
- name: Verify the published Phar runs and reports the right version
env:
VERSION: ${{ steps.vars.outputs.version }}
run: |
cd verify
PHAR_VERSION="$(php "wp-cli-${VERSION}.phar" --version | awk '{print $2}')"
if [ "$PHAR_VERSION" != "$VERSION" ]; then
echo "::error::Published Phar reports '$PHAR_VERSION' but the release is '$VERSION'."
exit 1
fi
php "wp-cli-${VERSION}.phar" --info
if ! jq -e . "wp-cli-${VERSION}.manifest.json" > /dev/null; then
echo "::error::wp-cli-${VERSION}.manifest.json is not valid JSON."
exit 1
fi
-
- - name: Import GPG key
- uses: crazy-max/ghaction-import-gpg@cb9bde2e2525e6865904e392450c3401a753db76 # v6.2.0
- with:
- gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
- passphrase: ${{ secrets.GPG_PASSPHRASE }}
-
- - name: Verify the published GPG signatures
- env:
- VERSION: ${{ steps.vars.outputs.version }}
- run: |
- cd verify
- gpg --verify "wp-cli-${VERSION}.phar.asc" "wp-cli-${VERSION}.phar"
- gpg --verify "wp-cli-${VERSION}.phar.gpg"This needs a new GPG_PUBLIC_KEY secret, or a fetch of the published key from a keyserver.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/post-release.yml around lines 116 - 148, Reorder the
workflow so the “Import GPG key” and “Verify the published GPG signatures” steps
run before “Verify the published Phar runs and reports the right version,”
ensuring no Phar command executes before signature verification. Update the
import step to use a GPG_PUBLIC_KEY secret or another public-key source, remove
the private-key and passphrase inputs, and retain the existing gpg verification
commands.
Nothing consumes it. wp-cli/wp-cli.github.com has no repository_dispatch workflow and no reference to the release-published event type, and createDispatchEvent returns 204 regardless of whether anything is listening, so the step went green either way. The site it targets is no longer maintained, so there is nothing to trigger. Removed the step, the now-unused TAG job variable, and the corresponding line in the release checklist. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xw2XwqE3C4cGGZ6EbS8p3V
The site these steps target is no longer maintained, so verifying its readme, updating homepage version references and tagging a website release are all dead work. The regular release checklist lost the same section earlier in this branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xw2XwqE3C4cGGZ6EbS8p3V
Homebrew switched autobump from an opt-in list to opt-out: every homebrew-core formula is now bumped by BrewTestBot unless it declares no_autobump! or a livecheck skip. Neither wp-cli nor wp-cli-completion opts out, so both are bumped without anyone doing anything, typically a few hours after the release is published since BrewTestBot polls every three hours. Both checklists now say to confirm the bump landed rather than to perform it, and keep the manual command only as a fallback. Also corrects the formula path, which moved to the sharded Formula/w/ layout, and drops a stray argument that made the bump command pass "-" to wget as a second URL. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xw2XwqE3C4cGGZ6EbS8p3V
The releases@wp-cli.org key pair is not going into GitHub secrets for now, so the workflows no longer import it: - `release.yml` promotes the Phar and generates the checksums, but does not sign, and attaches 5 assets to the drafts instead of 7. - `post-release.yml` verifies the published checksums, the Phar itself and that it matches the stable build, but no longer verifies signatures. Neither workflow needs GPG_PRIVATE_KEY or GPG_PASSPHRASE anymore. Signing goes back to being a manual step, so the release checklist gets it back: sign the stable Phar in wp-cli/builds by hand and attach the .gpg and .asc files to both drafts before publishing. The commit step in release.yml now lists the checksum files explicitly rather than globbing phar/wp-cli.phar.*, so that the signatures are only ever committed by whoever produced them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXzEZqS2c8xEKQkfkTycyw
schlessera
left a comment
There was a problem hiding this comment.
Thanks for the work on this, @swissspidy.
Request changes. The PR respects the manual signing, but it publishes the stable Phar before the signature, so the signing step no longer gates anything. That, the tag trigger and the rerun paths are the blocking items; the specifics are in the inline comments.
Concurrency controls are already in place; keep them. Assign this PR to the 3.0.0 milestone.
Next step: add coverage for the failure and recovery paths named inline (partial tag push, lookup error, published-release rerun, changed bytes after signing, post-bump rerun, lock freshness, missing deb), then a dry run showing a failed run leaves stable untouched and a rerun finishes without moving tags.
| on: | ||
| push: | ||
| tags: | ||
| - 'v*.*.*' |
There was a problem hiding this comment.
A tag push runs this workflow from the tagged commit even when that commit never reached main, and there is no protected-tag ruleset or release environment on this repository. Anyone with write access can tag a commit that carries a modified workflow and use ACTIONS_BOT to push to wp-cli/builds and create the releases. The X.Y.Z check at :64 only filters suffixes, it does not close this.
Either dispatch from reviewed main only, or protect v* tags and put this job in a release environment with required approval.
| name: release-notes-${{ steps.vars.outputs.version }} | ||
| path: release-assets/*.md | ||
|
|
||
| - name: Promote release Phar to stable & generate checksums |
There was a problem hiding this comment.
The stable Phar, manifest, hashes and the deb symlink are promoted here and pushed at :232 before the checklist's signing step (4-REGULAR_RELEASE_CHECKLIST.md:110). Fresh installs and wp cli update --stable get unsigned bytes for as long as signing takes, and post-release never checks that a signature exists, so the manual signing no longer gates anything.
Prepare the draft assets first, sign locally, verify against the trusted public key, then publish Phar, manifest, hashes and signatures together. The parity check in post-release.yml:146 compares the release asset with stable, so it moves after that promotion. The Phar verification at :99-123 stays where it is.
| cd builds/deb | ||
| DEB_FILE="php-wpcli_${VERSION}_all.deb" | ||
|
|
||
| if [ ! -f "$DEB_FILE" ]; then |
There was a problem hiding this comment.
A missing deb only warns, and the bundle deployment builds the deb last (wp-cli-bundle deployment.yml:263,352), so a release cut right after the Phar appears leaves php-wpcli_latest_all.deb on the previous version. Wait for the full deployment and fail when the package is missing.
| mkdir -p release-assets | ||
| cd wp-cli-dev | ||
|
|
||
| vendor/bin/wp maintenance contrib-list --format=markdown > ../release-assets/contributors.md |
There was a problem hiding this comment.
Neither generator is tied to the release being cut. contrib-list drops the first open milestone (.maintenance/src/Contrib_List_Command.php:100,106) and then iterates what remains, and there is exactly one open 3.0.0 milestone in framework, bundle and handbook right now, so contributors from all three are omitted. Both commands read the lock of the last closed bundle milestone (Contrib_List_Command.php:123-135, Release_Notes_Command.php:110-129), so packages added since (ability, ai, block, site-health) are missing. WP_CLI::log writes to stdout, so the empty-file guard at :150 never fires.
Pass the release milestone and the bundle lock at the new tag explicitly, and land the contrib-list fix in wp-cli-dev before relying on this output.
|
|
||
| if gh release view "$TAG" --repo wp-cli/wp-cli >/dev/null 2>&1; then | ||
| echo "Release $TAG already exists on wp-cli/wp-cli. Updating..." | ||
| gh release edit "$TAG" \ |
There was a problem hiding this comment.
gh release view only checks existence. gh release edit --draft turns a published release back into a draft, and upload --clobber then replaces assets users already consumed; a rerun or a manual dispatch for an older tag rewrites a published release. Same at :334 for the bundle.
Query isDraft, update only an existing draft, keep its notes, and abort on a published release. On rerun compare the Phar bytes with what was already promoted: unchanged bytes keep their signatures, changed bytes for a version that was already signed fail.
| # milestone closing and the website dispatch below still need to run. | ||
| if [ "$CURRENT_BASE" = "$VERSION" ]; then | ||
| SHOULD_BUMP=true | ||
| elif [ "$CURRENT_VERSION" = "$NEXT_ALPHA" ]; then |
There was a problem hiding this comment.
Once the framework bump has landed, a rerun takes this branch, should_bump=false skips the bundle reset at :254, and a run that failed between the two pushes cannot finish. Decide the bundle step from the bundle's own state.
| git diff --staged | ||
| else | ||
| git commit -m "Bump version to $NEXT_ALPHA" | ||
| git push origin main |
There was a problem hiding this comment.
This push is rejected by the current main branch protection: one approved PR is required and the default Actions token has no bypass. Decide explicitly whether this opens a PR or uses a dedicated bot identity with a documented, scoped bypass.
| NEXT_ALIAS: ${{ steps.vars.outputs.next_alias }} | ||
| run: | | ||
| cd wp-cli-bundle | ||
| composer require "wp-cli/wp-cli:dev-main" --no-interaction --no-install |
There was a problem hiding this comment.
composer require writes the lockfile first, then composer config at :260 changes extra.branch-alias, which is part of Composer's content hash, so the committed lock is already stale (composer validate fails on current bundle main with these exact commands). Set the alias first, or run composer update --lock --no-install after both, and finish with composer validate.
|
|
||
| # Registered as `maintenance release` in wp-cli-dev's bootstrap.php, | ||
| # with `close-released` as a subcommand of that. | ||
| vendor/bin/wp maintenance release close-released --bundle |
There was a problem hiding this comment.
close-released --bundle builds its list from the bundle lock's packages (.maintenance/src/Release_Command.php:308), so wp-cli/wp-cli, wp-cli/wp-cli-bundle and package-command (require-dev) are never closed. Pass them explicitly.
| ### Updating the Phar build | ||
| ### Updating the Phar build & Publishing GitHub Releases | ||
|
|
||
| - [ ] Create a PR from the `release-x-x-x` branch in `wp-cli/wp-cli-bundle` and merge it. This will trigger the `wp-cli-release.*` builds. |
There was a problem hiding this comment.
Merging the bundle PR does not trigger the release build: the push to release-** does, merging to main builds nightly (wp-cli-bundle deployment.yml:5-8,221).
…ssing packages Addresses the CodeRabbit review on #6352. prepare-release.yml: - Fail closed when `git ls-remote` errors instead of treating a failed lookup as "tag does not exist". - Accept an existing tag only when it already points at the commit being released, and push only the missing tag, so a rerun after a partial push can finish. A tag pointing elsewhere is fatal; tags are never moved. - Stop persisting credentials on the read-only wp-cli/builds checkout. release.yml: - Wait for the bundle deployment to finish (the Debian package is built last) before promoting anything, and fail when the package is missing instead of leaving php-wpcli_latest_all.deb on the previous release. - Skip, rather than fail, on a pushed non-stable tag; a non-stable tag on workflow_dispatch still fails. - Reduce the default token to contents: read (all writes use ACTIONS_BOT), stop persisting credentials on the wp-cli-dev checkout, and bound the org-wide composer install with timeout-minutes. post-release.yml: - Decide the bundle reset from the bundle's own composer.json rather than should_bump, so a rerun after the wp-cli bump can still finish it. - Set the branch alias before `composer require` writes the lockfile, otherwise the committed lock is stale; verify with `composer validate`. - Reduce the default token to contents: read, stop persisting credentials on the read-only checkouts, bound the milestone step and skip its composer install on dry runs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AmVGVSRR8Spk5ABae46VoC
Needs rigorous testing. Requires some new secrets too.
Required secrets & permissions
These have to be in place before this can run against a real release.
Secrets
ACTIONS_BOTNo GPG secrets are needed. The
releases@wp-cli.orgkey pair is deliberately not uploaded to GitHub for now, so signing the stable Phar and attaching the.gpg/.ascfiles to the drafts stays a manual step in the release checklist. The workflows generate and verify checksums only.ACTIONS_BOTscope onwp-cli/wp-cliPreviously this token was only used against other repositories (
wp-cli/builds,wp-cli/wp-cli-bundle). It is now also used againstwp-cli/wp-cliitself, and needs to be able to:prepare-release.ymlcreates the release tags.GITHUB_TOKENcannot be used here: pushes made with it do not trigger further workflow runs, sorelease.ymlwould never start.release.ymlpreviously usedGITHUB_TOKENfor thewp-clidraft release andACTIONS_BOTfor the bundle one; both now useACTIONS_BOT.main—post-release.ymlcommits the version bump.mainis branch-protected, so the bot has to be a bypass actor on that branch, otherwise the post-release bump fails. This was already true of the originalGITHUB_TOKENversion of that step, which would not have worked either.Worth considering as a follow-up: replacing the long-lived
ACTIONS_BOTPAT withactions/create-github-app-token, so each run gets a short-lived, per-repository token instead of one credential with write access across several repositories.Summary by CodeRabbit
New Features
Documentation