Repository navigation
First release since v1.1.1 (2026-01-23).
The README plugin table maintains itself
The table between the PLUGIN-VERSIONS markers was hand-maintained, despite the markers implying otherwise, because Update Versions staged only marketplace.json. It had drifted badly: five version cells wrong, six rows advertising plugins present in no marketplace, and seven real entries missing.
scripts/render_readme_table.py now regenerates that block from .claude-plugin/marketplace.json, which becomes the single source of truth for both the versions and the plugin list. The workflow runs it on the same repository_dispatch a plugin release already fires, and stages README.md alongside the manifest. validate.yml runs it with --check, so CI fails if the block is hand-edited or a manifest change lands without a regenerate, and names the command to fix it.
The marketplace has its own version
marketplace.json carried name, owner, metadata and plugins but no version — the one unversioned artifact in a repo whose job is tracking versions. It is now 1.2.0, top level, which is where CPV's REQUIRED_MARKETPLACE_FIELDS looks and which claude plugin validate --strict accepts.
Workflow hardening (#5)
Two injection paths, where untrusted values were spliced into run: script text:
client_payload.*, attacker-controlled onrepository_dispatch, at five sites — including a python heredoc where a single quote closes the literal and the rest executes, in a job holdingcontents: writeandMARKETPLACE_PAT.$VERSION, parsed from aplugin.jsonfetched over the network from each listed plugin repo, spliced into the heredoc that writesmarketplace.json. Reachable without the PAT.
Both now travel through env:. A payload shaped to close the old literal is stored as inert data.
Two silent-failure paths, both made loud:
- The version fetch ran three stages under
2>/dev/nullwith an|| echo ""fallback, collapsing a 404, a rate limit, an expired PAT and a malformed manifest into one empty string the next test read as "nothing to update". An expired token would have quietly stopped the whole marketplace from updating while every run stayed green. - The push retry loop exhausted three attempts and then fell through, reporting success on a push that never landed.
Fetch and parse are now separate so each failure reports its own cause, and a conflicting rebase unwinds cleanly and stops instead of being papered over.
Upstream
Filed Emasoft/claude-plugins-validation#232: CPV reads its intentional-divergence declaration from a top-level cpv key that claude plugin validate --strict rejects as an unknown field, so the declaration cannot be made by any plugin whose pipeline runs the native strict validator.