Repository navigation
Conversation
Smoke-tested 1.2.1 on WordPress 7.1.2 / PHP 8.5 (activation, settings and connector status, add repository with initial generation, editor panel and Regenerate, version picker with a backdated draft): no PHP notices, no console errors, every generated block valid. Core's AI Client SDK is unchanged between 7.0.6 and 7.1.2, and nothing the plugin calls is newly deprecated. bin/ holds two one-time SVN maintenance scripts that have been shipping inside the WordPress.org package since the first release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The HTML-to-blocks splitter ended an element at the first closing tag of ANY container type, so a nested list, a <p> inside a <blockquote>, or code inside a list item was cut in half and the remainder became a broken paragraph; and code blocks and quotes were emitted without the classes core requires, so every one showed "This block contains unexpected or invalid content". Headings and lists only loaded through deprecations and kept the bare markup on the front end. Elements now run to their own closing tag (counting nesting), and each block is built the way core serializes it: headings with their class, lists as list-item blocks with nested lists inside their item, quotes holding paragraph blocks, <pre><code> as a code block and bare <pre> as preformatted, tables with auto layout. Markup a list item cannot hold is flattened into it; a paragraph wrapped around block markup is unwrapped; an unclosed element is closed where the next block starts. Validated against the real 7.0.6 and 7.1.2 editors: 26 realistic fixtures, zero invalid blocks and zero blocks needing a deprecation (the old output fails the same check). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Applied to the post body and the excerpt before KSES:
- A "<" that does not open an HTML element is text. KSES deletes from
such a "<" to the next ">", so "Requires PHP < 8.2 or WordPress
>= 6.0" was saved as "Requires PHP = 6.0", and array<string> lost its
type. Core's own pre-KSES pass only catches a "<" with no ">" after it.
- HTML comments are dropped: the model never needs one, and a block
delimiter smuggled in through release notes (<!-- wp:rss {…} /-->)
rendered as a live dynamic block with the attacker's attributes.
Block markup the model writes itself is no longer wrapped twice.
- "[" is encoded, so a shortcode quoted in release notes ([gallery
include=…]) is shown as text instead of running on the site.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1.2.1's final-beats-pre-release guard also caught post-releases — a re-release of the same version published as final (1.5.0-1, -p1, -patch.1, -post1) — so it was ranked below the bare version and never posted, where 1.2.0 posted it. PHP already ranks those above the release, correctly; the guard now leaves them to version_compare() and still fixes -pre/-preview and the rc-with-build-metadata case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GitHub identifiers are case-insensitive and generated release notes link with the canonical casing, so a repo stored as acme/plugin had its own Acme/Plugin pull requests fetched anonymously — a 404 for a private repo, losing the context — after 1.2.1 started fetching off-repo links without the token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1.2.1's whole-word matching stopped "rce" matching inside "source", but also stopped "CVEs", "injections" and "authentication bypasses" from classifying a release as security, which substring matching caught. Plurals (and -ies) are accepted again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1.2.1 deleted the whole summary at the start of every run so failing repositories would not stack errors forever, which also discarded "drafts created" entries the admin had not seen yet — any retry run or hourly schedule wiped them. Only the error list is reset now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Off-repo links have been fetched anonymously since 1.2.1, drawing on the server's shared per-IP quota. Exhausting it scheduled a full retry run, wrote an unconditional error-log line, and overwrote the site's remaining-count. An anonymous lookup now just fails. The exhaustion log line is also gated on WP_DEBUG like every other log line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Raising the token-status cache to 15 minutes in 1.2.1 also cached timeouts and GitHub outages as "invalid" for 15 minutes. Only a definitive answer (valid, or a 401) is cached that long now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1.2.1 started showing the notice, but nothing cleared it: after the release generated successfully, or its repository was removed, the screen still said generation "keeps failing" for a day. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 1.2.1 handler submitted on the Enter that commits an IME composition, and once per repeat while Enter was held. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WordPress.org's Plugin Check reports hidden files as an error, and a reviewer flagged this one before 1.0.1; the CI deploy brought it back. The empty languages/ directory still ships, so Domain Path stays valid. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also narrows the 1.1.x upgrade bullet: only the package the old version was tracking carries its cursor over. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Neither the version picker nor the "post already exists" dialog listened for its cancel event, so pressing Escape closed it with the row's confirm handler still attached to the shared button. The next confirmation on any row then also ran the declined action — for the conflict dialog, regenerating (and replacing the content of) a published post the admin had just declined. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
generate_text() returns whatever text the provider sent, even when it stopped at the token limit or a content filter: a post cut off mid-sentence and mid-list was saved and, on repositories set to publish, published and emailed. The connector now asks for the result, checks the finish reason, and also refuses a response with a title but no body (a refusal, or a reply that stopped after the metadata lines). Each is a generation failure: recorded, retried, never cached. Also points the unavailable-client message at Settings → Connectors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Some models wrap HTML output in ```html … ```. Around the whole response the fence became the post title; around the body it became visible paragraphs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Subsites of a network activation (and Composer or mu-plugin installs) never ran the activation hook, so their option rows did not exist. On a missing row update_option() sanitizes twice, so the first saved GitHub token was encrypted twice and every request then failed, and an unchecked box could not be saved at all. The first-boot self-heal that schedules the check now writes the defaults too, and the token sanitizer keeps a value that is already its own ciphertext. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A run that died mid-way left queued entries that the next run posted even if the repository had since been paused or removed; nothing is lost by discarding them, since cursors advance only after a post exists. A run stopped by GitHub's rate limit now says so in the run summary instead of leaving it empty, run after run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Requests never follow redirects (that would replay the token), so a renamed repository failed every check with a bare "HTTP 301", and adding it under its old name succeeded and then waited forever. A redirect now gets an error that says what happened, and the add is rejected with the same explanation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The filter ran after force_draft was resolved, so a documented use
("publish major releases") also published a manual Generate draft
immediately and reset its backdated date to now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The disclosure appended to posts still named the pre-rename product. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wp_set_script_translations() was never called for the editor script, so the GitHub Release panel stayed English on translated sites. Help text no longer refers to a Save Repositories button, names pinned model versions, or says every regenerated post keeps its slug. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The external-services section listed repository metadata the plugin never sends and omitted the linked issue and pull request text it does send (WordPress.org guideline 7). Also corrects the research-depth and email-notification descriptions, the pinned model names, and how the check-frequency filter takes effect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a check frequency that is not a registered WP-Cron schedule, scheduling fails on every request, so the defaults written on first boot would have been rewritten (and their rows re-read) on every request too. They are written once the event is actually scheduled — exactly once per site. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review R1: discarding the whole queue at the start of a run lost a release for good. A scan only generates each stream's newest release, so a queued v1.1.0 was never detected again once v1.2.0 shipped before the next run. Queued entries are kept again; process_queue() skips only entries that are no longer eligible — the repository removed or paused, a pre-release with pre-releases off, or a package outside the current selection — and runs duplicates once. Queue entries now record the pre-release flag so that check is possible. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review R2-R4: the converter changed what posts said while producing valid blocks. A list item saves its text before its nested lists, so text after a nested list moved ahead of it; paragraph boundaries inside an item ran text together; per-item <li value> numbering and a heading's id (an in-post link target) were dropped. A list core/list-item cannot represent faithfully — text after or between nested lists, <li value>, a list type, block markup inside an item — is now kept as an HTML block, exactly as written. Paragraph and code boundaries inside items become line breaks; line breaks the model wrote are untouched. Heading ids are kept as core anchors. Also bounds the work on hostile input (review follow-up): lists and quotes nested deeper than 16 levels stay HTML instead of recursing, and an element with no closing tag anywhere after it no longer rescans the rest of the content (10,000 unclosed paragraphs: 4.8 s to 8 ms). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up: an error or tool-call finish reason was still accepted as a post. Anything but a normal stop is now a generation failure, with the cut-off and content-filter cases keeping their specific messages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Native blocks are now built only for shapes that map onto a core block as written: paragraphs, headings, flat lists of inline items, quotes made of paragraphs (or bare text), code, tables, separators, images. Anything else (a nested list, block markup inside a list item, per-item numbering, a paragraph wrapped around block markup, a quote holding a list or a citation) is kept as one HTML block exactly as written. This replaces the list-item reshaping, the recursion through quotes and paragraphs, and the nesting cap with a single fallback that cannot reorder or drop content, and makes depth cost no more than length. Elements are still matched to their own closing tags, so nothing is cut in half. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A release left in the queue by an interrupted run is generated like any other, unless its repository has since been removed or paused. The re-checks of the pre-release setting and package selection, the de-duplication (a repeat entry already finds the cached response and the existing post), and the extra pre-release field on queue entries are dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The anchor was matched with a pattern that accepted only word characters and searched the whole opening tag, so a valid id such as "c++", "café", "config[api]", or one written with a character reference was dropped, and "id=" text inside another attribute (title="An id=example") was taken as the id. In-post links to those headings lost their target. The id is now read by the DOM parser the converter already uses for image attributes, decoded as a browser reads it, and written back with core's block-attribute serializer and esc_attr(). Co-Authored-By: Claude Opus 5.5 <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.
Follow-up to the 1.2.1 merge, from a fresh six-reviewer pass over 1.2.1 plus a WordPress 7.1 compatibility check. One commit per change (30) so any one can be dropped; every commit passes the suite on its own (checked in a throwaway worktree).
WordPress 7.1 and packaging
Tested up to: 7.1. Core's AI Client SDK is byte-identical between 7.0.6 and 7.1.2, 7.1's connector changes are additive, and nothing the plugin calls is newly deprecated. Smoke-tested end to end in isolated WordPress Playground sandboxes on 7.1.2 / PHP 8.5 and 7.0.6 / PHP 8.2, with a mocked Anthropic API.bin/andlanguages/.gitkeep(Plugin Checkhidden_fileserror). The emptylanguages/still ships.Generated content (pre-existing since at least 1.2.0)
<: "PHP < 8.2 or WordPress >= 6.0" was saved as "PHP = 6.0".wp:rssblock rendered live.Regressions introduced by 1.2.1 (all low severity)
1.5.0-1,-p1,-patch.1) were never posted.Pre-existing issues, contained fixes (the batch Jake approved)
ghrp_post_statusfilter would publish it.Changelog updated under 1.2.1.
Deferred (need design decisions):
nightly/latesttags hiding versioned releasesmodule/vXtagsReview round 1 (Codex), four blockers — all fixed:
a69e199), and sincee70dd36only entries for a removed or paused repository are skipped.<li value>numbering and heading ids. Heading ids are kept as anchors (f351d3a), and sincebab7f3dany list or quote with no 1:1 block stays one HTML block, exactly as written, instead of being reshaped.2e01d40)<inside atitleattribute) no longer produces an invalid block, but KSES garbles its text exactly as on main.Review round 2 (Codex delta recheck at
e70dd36): R1–R3 closed, and both queue deviations and the native-or-HTML boundary accepted. R4 was only partly fixed: heading ids were matched with a word-character pattern anywhere in the opening tag, soc++,café,config[api], or an id written with a character reference was dropped, andid=text inside atitlewas taken as the id. Ids are now read with the DOM parser the converter already uses for image attributes and written back with core'sserialize_block_attributes()andesc_attr()(2db1297). Codex's anchor probe passes on 7.0.6 and 7.1.2 after real KSES (main and the fix exit 0,e70dd36exits 1); only those five fixtures' output changed, and each validates and round-trips byte-for-byte.Simplification after round 1. Asked whether the round-1 fixes added unhelpful complexity, two did, and both are now reduced to the minimum that fixes the reported defect:
bab7f3d): native blocks only for shapes that map 1:1 onto a core block as written: paragraphs, headings, flat lists of inline items (start/reversedkept), quotes made of paragraphs or bare text, code, tables, separators, images. Everything else (a nested list, block markup inside an item,<li value>,type, a<p>wrapped around block markup, a quote holding a list or a citation) is one HTML block, exactly as written. The list-item reshaping, all recursion, and the nesting cap are gone.e70dd36): entries are skipped only when their repository has been removed or paused. The pre-release and package re-checks, the de-duplication (a repeat entry already hits the cached AI response and the existing post), and the extra queue field are gone;Release_Queueis back to main's version.save()returns null and its content is no longer sourced from the markup, so any validator that compares againstsave()flags every HTML block. Those blocks parse as valid, round-trip byte-for-byte, and need no deprecation; on 7.0.6 they validate cleanly.Verification:
composer test: 518 tests / 862 assertions / 1 pre-existing skip.vendor/.git diff --check, andlint:jsall exit 0.dist/matches a fresh build.🤖 Generated with Claude Code