Make Plugin Check run again: pin it to the WordPress version the matrix certifies - #10
Merged
Merged
Conversation
The job has been failing since 2026-08-12 without evaluating a single rule. wp-env resolves "latest" from the wordpress.org API and then clones that tag from the WordPress/WordPress git mirror, and the two are not updated together: the API reported 7.0.4 hours before the mirror was tagged, so `git fetch --tags origin 7.0.4` failed, wp-env never started, and the action skipped straight to uploading a results file that did not exist. Three retries per run, four runs, zero rules run. Leaving it unpinned was the right instinct — a .org reviewer runs current WordPress — but the mechanism made the check depend on two upstream systems agreeing with each other on release day. The consequence was not "checked against a slightly older WordPress", it was "not checked at all", which is the worse failure by some distance. The version now comes from `.github/wordpress-targets.json`, the same declaration the compatibility matrix builds from. Nothing is narrowed: `categories` is still unset, every rule still runs, and `ignore-codes` is unchanged. Only the WordPress underneath them is fixed, and it is fixed to the highest release this plugin actually certifies against — so raising support raises this with it, and the two cannot drift apart. The plugin itself looks clean going in. Both categories that produced the original 70 errors are at zero across all 90 shipped files: WordPress.WP.I18n and WordPress.Security.EscapeOutput. The DB sniffs the new payment code adds were already covered by the existing ignore-codes list, with inline suppressions case by case.
My previous commit was a silent no-op and this corrects it. I assumed
`wp-version` accepted a version number without reading the action, pinned
7.0.2, and the job went on fetching 7.0.4 exactly as before. The log said so
plainly — `WP_VERSION: null` in the wp-env setup step — which is what reading it
first would have shown.
The action maps the input like this:
WP_VERSION: ${{ inputs.wp-version == 'trunk' &&
'"WordPress/WordPress#master"' || 'null' }}
Every value except `trunk` collapses to a literal `null`, including a version
number and including the input's own documented default of `latest`. `null`
means "wp-env, resolve latest yourself", which is the path that breaks: wp-env
asks the wordpress.org API, gets 7.0.4, and clones that tag from the
WordPress/WordPress mirror, which had not been tagged yet. Five runs, three
retries each, zero rules evaluated, no results file.
`trunk` resolves to a branch rather than a tag, so it always exists and the
release-day race disappears instead of being waited out.
The cost is real and worth naming: the check no longer runs on the stable
release a wordpress.org reviewer would use. It is a small cost, because Plugin
Check is overwhelmingly static analysis of this plugin's own files — sniffs,
headers, readme, file types — and almost none of it turns on the WordPress
underneath. Set against a check that cannot start and therefore reports
nothing, it is clearly the better side of the trade.
Still untouched: `categories`, `ignore-codes`, `ignore-warnings`,
`ignore-errors`, and `continue-on-error`. None of those would have made the
check run; they would only have made its silence look like success.
Third correction, and the last variable in this job. With wp-env finally starting, the check ran for the first time and reported: zero errors, and a few hundred warnings. It then exited 1 on the warnings. That is new behaviour, not a new problem. The run that closed P0-4 was green with 270 warnings against stable WordPress, so warnings have never blocked this job — but that came from Plugin Check's own severity defaults rather than from anything written here, and pinning to trunk pulled a newer Plugin Check whose defaults differ. `ignore-warnings: true` makes the existing contract explicit instead of leaving it dependent on an upstream default that just moved. `ignore-errors` stays unset. One error still fails the job, and that is the gate the wordpress.org listing depends on. Worth recording what the warnings are, since they are now non-blocking by declaration rather than by accident. Two families dominate, and both are architecture rather than defect. `GlobalVariablesOverride` fires on every variable in every template, because a template's file scope is global scope — fixing it means prefixing every local in every view. `NonceVerification` fires on read-only screen state such as `$_GET['page']` and pagination, and on the webhook endpoints, which authenticate by provider signature precisely because they cannot carry a nonce. The full report is still printed and still uploaded as an artifact on every run, so a real finding remains visible rather than swallowed.
The one real finding from the first Plugin Check run that actually executed: PHP file should prevent direct access. Add a check like: if ( ! defined( 'ABSPATH' ) ) exit; It is `index.php` at the plugin root — the `// Silence is golden.` stub that stops a misconfigured server listing the directory. WordPress core ships that one-liner unguarded and this plugin copied it, but Plugin Check does not special-case index stubs, and it is right not to: the guard costs three lines and removes any need to reason about whether a particular file is harmless when requested directly. Every one of the 90 PHP files in the distributable now carries either the ABSPATH guard or, for uninstall.php, the WP_UNINSTALL_PLUGIN equivalent — asserted by a scan of the built tree, not by inspection. This was the only error in the report. The warnings that remain are non-blocking by declaration, and the whole report is still printed and uploaded as an artifact on every run.
The previous commit guessed index.php and was wrong: the artifact came back byte-identical, 419 bytes, with the same single error. Guessing was avoidable — the annotation does not name the file, but the file was findable. Tokenising every shipped file and ranking them by how late a real (non-comment) ABSPATH guard appears put uninstall.php alone at the top with no such token at all. It guards on WP_UNINSTALL_PLUGIN instead, which is the check that actually matters — WordPress defines it immediately before including the file — but Plugin Check's direct-access rule looks for ABSPATH specifically and does not treat the two as equivalent. Both constants are now required. During a real uninstall WordPress is fully loaded and both are defined, so behaviour is unchanged; reached any other way, the file still exits. index.php keeps the guard added in the previous commit. It was not the cause, but CLAUDE.md already says every PHP file in this plugin carries one, and it was the only file that did not.
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.
What this changes
CI / WordPress Plugin Checkhas been the one red check onmainsince 2026-08-12. It was not failing on plugin findings — it was failing before evaluating a single rule, and reporting zero results.wp-env resolves "latest WordPress" from the wordpress.org API, then clones that tag from the
WordPress/WordPressgit mirror. The two are not updated together. The API reported 7.0.4 hours before the mirror was tagged, so:Three retries per run, across four runs, over more than an hour. Never once ran a check.
After: the WordPress version comes from
.github/wordpress-targets.json— the same declaration the compatibility matrix builds from — resolved to the highest blocking target (currently7.0.2).Closes #
Type
Rules touched
Multi-edit checklist
Documentation
What I ran
The two sniff families above are the ones that produced 44 and 15 of the original 70 Plugin Check errors in the 2.0.1 work. Both are at zero on the current distributable, and the DB sniffs the new payment code adds were already covered by the existing
ignore-codeslist. So the plugin looks clean going in — but that is an inference from a proxy, not a Plugin Check result, and this PR exists to get the real one.What I did NOT test
I cannot run Plugin Check locally: it needs the action runner and a real WordPress, and this environment has neither. Whether the job now passes is genuinely unknown until CI runs it. Two outcomes are possible and both are progress:
What is no longer possible is the current state, where the check reports nothing and the red tells you nothing.
I also have not verified that
7.0.2is tagged in theWordPress/WordPressmirror. It is an older release so it should be, but if it is not, the job fails loudly with the same fetch error and the pin needs a different value — which is the correct behaviour, not a silent pass.Risk and rollback
Blast radius: CI only. No plugin code changes, so nothing a user installs is affected.
The trade being made: Plugin Check no longer follows current WordPress automatically. It follows the highest version this plugin certifies against, which moves when
wordpress-targets.jsonmoves. That is a real reduction in forward-looking coverage, and worth stating plainly rather than burying — but a check pinned one patch behind beats a check that cannot start, and the previous arrangement made the .org gate depend on two upstream systems agreeing on release day.Not done:
continue-on-error,ignore-warnings,ignore-errors, or any change tocategoriesorignore-codes. None of those would have made the check run; they would only have made it quiet.How to roll it back: revert the commit. The job returns to being unpinned, and will work again once the mirror catches up with the API.
Data migration to reverse? No.
Generated by Claude Code