Plugin Directory: Resolve release holds from the stable tag - #807
Closed
obenland wants to merge 15 commits into
Closed
Plugin Directory: Resolve release holds from the stable tag#807obenland wants to merge 15 commits into
obenland wants to merge 15 commits into
Conversation
A release block or cooldown was resolved by looking up the release named
after the plugin's Version header. Renaming the header inside a held tag
orphaned that lookup — no release found, so no block and no cooldown —
and the tag's rebuilt package shipped immediately under the new version.
Resolve the current release from the stable tag first, the source of the
served package, falling back to the version for trunk releases, which
are keyed trunk@{version}.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Member
Author
|
@Luc45 This moves the version lookup from |
Review fixes:
- get_current_release() no longer falls back to the header version for
tagged plugins (only trunk-stable ones, which are keyed trunk@{version}),
closing a fail-open that reverted tagged plugins to header-keyed
resolution and removing a redundant get_release pass per plugin.
- block_release()'s already-served guard and force_release()'s audit note
now key on the resolved release's version instead of the header meta.
- Both cooldown notices name the resolved release; the committer notice
computes its window from compute_release_time() to match enforcement.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…der.
Second review round:
- get_current_release() matches the release strictly by tag instead of
through Plugin_Directory::get_release(), whose loose == collapses
numerically-equal tags ('1.4'/'1.40') and whose tag-first lookup lets a
dormant tag shadow the trunk@{version} row.
- The block hold no longer gates on the header-derived is_new_version, so a
header renamed to match the served version can't write the blocked tag live.
- block_release()'s already-served guard no longer reads empty === empty as
a match.
- The cooldown UIs resolve and key off the stable-tag release: the metabox
force-release staleness token is the release tag (not the header version),
and both notices no longer bail on an empty header version, so an enforced
hold stays visible and liftable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
block_release()'s already-served guard compared the resolved release's
version against update_source.version, while the release identity is the
tag: a re-commit into the served tag with a bumped header missed the
guard and recorded a block that held nothing. The guard now compares by
identity — the ref against the row's stable_tag for tagged releases,
falling back to the version for ref-less trunk-stable rows — via a
get_served_row() helper that get_served_version() now delegates to.
add_release() looked up the existing release through get_release(),
whose wp_list_filter() match is loose ('1.4' == '1.40'): the write could
merge onto a numerically-equal different release and, through the strict
dedupe below it, delete the genuine row. The lookup is now a strict tag
match, consistent with the dedupe.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
obenland
force-pushed
the
release-resolution-mismatched-header
branch
from
August 14, 2026 16:55
74c9040 to
7b89c7f
Compare
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
render_cooldown_status() was the last copy of the cooldown-clock arithmetic still on the old inline formula (latest confirmation, else the release row's date), so the releases listing could show a serve time that disagreed with the metabox and the frontend notice on the same screen. It now derives the window from compute_release_time(), the anchor update_single_plugin() actually gates on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
For plugins without a version_date meta, compute_release_time() fell back to post_modified, which moves on any post edit: a moderator note or term change would re-arm the cooldown displays for a version live for months. The fallback is now the release row's own date, which is fixed at creation; post_modified remains only as a last resort when there is no release row at all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
obenland
force-pushed
the
release-resolution-mismatched-header
branch
from
August 14, 2026 17:09
83ef9d7 to
07d3401
Compare
Its last production caller moved onto get_served_row() when the already-served guard became identity-based; the tests now read the row directly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
render_cooldown_status() runs per row in the releases listing, but compute_release_time() prefers the plugin-wide version_date, so after a new commit every superseded row with a release_delay re-displayed a pending serve time. A superseded row is never served; skip everything but the release the stable tag resolves to. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both walked get_releases() with the same strict tag comparison, with nothing persisted in between; one fetch now captures the existing release and removes every same-tag entry on the way. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
render_cooldown_status() called get_current_release() per release row, and that call walks all releases — O(N^2) over a plugin's release count. The row in hand only needs its tag compared against the current target, so the target derivation moves into get_current_release_tag() and the walk disappears from the listing entirely. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A stable tag flipped to trunk at an unchanged version creates no
trunk@{version} release row, so get_current_release() returned false —
and a false release fails every gate open: release_delay reads 0,
is_release_blocked() reads false, and the row is rewritten to serve
trunk, whose bytes are where the blocked tag was cut from. The strict
ref lookup now falls back to the version-named, then version-carrying,
release; the fallback only fires on a miss, so it can never redirect a
hold away from a ref-resolved release.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The listing's cooldown line compared rows against the strict get_current_release_tag() while get_current_release() gained a fallback, so in the trunk-flip state the listing suppressed the line for the release the metabox showed as current. single_plugin() now resolves get_current_release() once and passes it down the rows — one walk per listing, one canonical notion — and the helper, whose only remaining caller was the resolver itself, is inlined back. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Trunk's baseline-scan commit added get_served_release() — the same row fetch this branch had introduced as get_served_row(). The trunk name and docblock win; get_served_version() stays dropped, as its last caller was the version-keyed guard this branch replaced. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
obenland
added a commit
to obenland/wordpress.org
that referenced
this pull request
Aug 14, 2026
A completed verdict was matched against the plugin's Version header before its release was held, which is the lookup WordPress#807 replaced: the header is author-controlled, so committing a rename into the scanned tag made the scan's version no longer the plugin's and the block was refused. Blocking now resolves the current release the way the updater does, from the stable tag, and applies only when that release is the one the verdict examined. The test environment gains WP_GANDALF_SCAN_SHARED_SECRET so the callback route can be driven by hand; the PHPUnit suite defines it itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The release cooldown and #785's release block are resolved by looking up the release named after the plugin's Version header:
update_single_plugin(),block_release(), andforce_release()all callget_release( $post, $version ), which matches by tag. An author can commit one line into a held tag — changingVersion: 1.4.4toVersion: 1.4.5— and the lookup finds no release for "1.4.5": no block, no cooldown, and the tag's rebuilt package is written toupdate_sourceimmediately under the new version label. The importer'sversion_tag_mismatchcheck is a warning, not a gate. The same rename can point at another, clean release's version to inherit its (absent) hold. With the release cooldown live in production, this is an active bypass of it today: the rename ships a held tag's rebuilt package immediately instead of waiting out the cooldown. It would equally bypass #777's scan-driven blocks once those land.Builds on #785; complements #777 and #806.
What it does
API_Update_Updater::get_current_release()resolves the release being served or held from the stable tag — the source of the served package, so the hold follows the content, not the header's label — falling back to the version for trunk releases, which are keyedtrunk@{version}. A rename inside a held tag now resolves to that tag's release, block and cooldown intact.update_single_plugin(),block_release(), andforce_release()use it, so enforcement, scan-driven blocking, and the reviewer's release all target the same release row. Force-release keeps working on a renamed release rather than failing to find it.1.4carrying version1.4.0) were previously invisible to the cooldown entirely; they now resolve correctly.Testing
tests/Current_Release_Resolution_Test.php(6 tests): a renamed header keeps the cooldown hold and keeps a block's hold, a rename pointing at another clean release's version still holds,block_release()lands the block on the stable tag's release, force-release serves the renamed version, and trunk releases still resolve by version. Verified the first three fail against the previous resolution.🤖 Generated with Claude Code