Technical review state: 2026-08-24. Product flows and boundaries are checked against Companion 0.4.117, Power 0.6.35, and CLI and Skills 0.4.86. Third-party details can change independently.
This document is for you once your connection is already up (wp-agent doctor reports all seven checks OK, see Getting started, section "Diagnostics"). It answers one specific question: the process runs through, but something about the result is still off, a post ended up empty, or it was created twice. Why, and how do I fix it?
First, the good news: in almost all the cases below, the abort you see is not a bug, it's WPAgently saving you from silent damage. You don't have to repair anything that's broken, you just tweak the input slightly and run it again. Each section tells you exactly what to do.
For a connection problem, on the other hand (site unreachable, revoked OAuth grant, invalid Application Password, bot in the wrong role, MCP server not responding), wp-agent doctor is the right first step, not this document. If Doctor and the client disagree, next run the external diagnosis under WPAgently > Connect agent. It checks the public path through the CDN, firewall, and security plugin without sending credentials.
How to read this document
Part 1 covers the five silent failures that WPAgently catches for you. For each one: what you see, why it happens, what WPAgently does about it in the background, and the exact wording it aborts with when you hit it. The "What's behind it" paragraphs are for the curious. If the symptom and fix are enough for you, feel free to skip them.
Part 2 covers additional special cases of individual features: WooCommerce products and orders, ACF fields, Contact Form 7, Gravity Forms, and page builder detection. These are not among the five core failures, but concrete individual cases you'll only run into if you use exactly those features.
Part 3 lists the read-only tools you can use yourself to check what is actually stored on a post, before contacting support.
Part 1: the five silent failures
Version coupling for combined pipelines
The article and landing pipelines check before the first remote mutation whether the connected Companion declares the flat remote_create and manual_only_update write semantics, together with the general manual_only annotation. If these metadata fields are missing or invalid, the CLI fails closed and reports that no content was changed. This affects existing Markdown updates as well as requested SEO and status steps. An older Companion is therefore not unconditionally compatible with these combined pipelines. Individual Abilities remain usable according to their installed schema. See also the compatibility matrix.
1. The article suddenly shows up unformatted (core/freeform)
What you see
The post or landing page appears in the editor as a single gray "Classic" block instead of clean, individually editable blocks. In the response, freeform_count is greater than 0.
For context: the modern WordPress editor (Gutenberg) builds content out of individual "blocks," one block per heading, paragraph, image, and so on. The "Classic" block is the fallback for old, unformatted content. If your whole article lands in one of those gray blocks, the formatting is lost.
Why it happens
The assistant supplied content that WordPress couldn't read as clean blocks (for example because raw HTML was written instead of Markdown). WordPress then doesn't throw an error; it silently stores the entire content as a single core/freeform block. That silent switch is exactly the trap. (Markdown is a simple text format with **bold**, ## heading, and lists; WPAgently builds the blocks from it server-side.)
What's behind it
wpagent_block_report() (packages/wp-agent-companion/includes/helpers.php) checks, for every parsed block, whether blockName is null and innerHTML is not empty: that is exactly the freeform signature. This check is reused from four places: create-post-from-markdown, update-post, render-check, and render-verify. The verified ARTICLE pipeline creates new article content exclusively through wp-agent/create-post-from-markdown. Landing pages use wp-agent/upsert-pattern. Both paths build valid blocks server-side instead of asking the assistant to formulate block markup.
Where the process aborts
The article and landing checks (packages/wp-agent/src/pipelines/article.mjs, landing.mjs) verify freeform_count twice: directly after conversion, and again through wp-agent/render-verify. The result step is named render-check internally. On a hit, the pipeline aborts with one of these patterns:
Aborted: N freeform blocks appeared during conversion. This prevents silently falling back to the old Classic editor.
Aborted: N freeform blocks appeared while building the landing page. This prevents silently falling back to the old Classic editor.
Aborted: the render check reports N freeform blocks.
Fix
Have the assistant write exclusively through create-post-from-markdown or upsert-pattern, never raw HTML or block markup directly. If a specific Markdown input causes trouble (for example complex nested lists or unusual tables the converter doesn't know), have it simplified and run again. The abort itself is intentional: it prevents a broken-formatting post from sitting there unnoticed as a draft, or even going live.
2. Your SEO change is not applied (the current SEO writer is manual-only)
What you see
set-seo-meta returns HTTP 409 with manual_only, and the title, description, or focus keyword remains unchanged. This visible failure is the expected response, not a silent success.
Why it happens
SEO fields are not stored as normal text but in provider-specific hidden fields. A complete atomic compare-and-write proof across the supported provider surface is not currently available. The writer therefore returns HTTP 409 with manual_only before any provider mutation instead of attempting a remote write.
What's behind it
wp-agent/get-seo-meta (packages/wp-agent-companion/includes/abilities/get-seo-meta.php) first detects which supported plugin is active and reads its effective provider fields. Rank Math and Yoast use their effective meta fields. AIOSEO is read through its own WordPress Ability. SEOPress is read through its native REST routes. set-seo-meta validates the same input and reads the state freshly, but returns HTTP 409 with manual_only across providers before writing any field.
| Plugin | Title | Description | Focus keyword |
|---|---|---|---|
| Rank Math | rank_math_title | rank_math_description | rank_math_focus_keyword |
| Yoast | _yoast_wpseo_title | _yoast_wpseo_metadesc | _yoast_wpseo_focuskw |
| AIOSEO | native aioseo-posts/seo-data-update Ability | same Ability | same Ability |
| SEOPress | native title-description-metas REST route | same route | native target-keywords REST route |
The fresh re-read and conflict hash protect a supervised manual change from using an outdated state. No private fallback fields are stored because the active SEO system would never output them. AIOSEO post-SEO reads are practically evidenced only on MariaDB with transactional InnoDB tables; MySQL and SQLite are outside this runtime evidence. Without a supported active plugin, the read and preflight paths fail closed with HTTP 409.
Where the process aborts
Aborted: the SEO details were not saved (plugin <plugin>).
Fix
For HTTP 409, inspect the error code first. wpagent_seo_plugin_missing requires active Rank Math, Yoast, AIOSEO, or SEOPress. With manual_only, make the desired change in the respective SEO plugin and then call wp-agent-get-seo-meta again. Do not retry set-seo-meta as though it had written anything. An HTTP 403 from AIOSEO or SEOPress means that the plugin's own role controls rejected the read or preflight. Grant the specific plugin permission instead of bypassing it.
Important for Yoast: see point 2.5 in part 2, "SEO read-back trap with Yoast." set-seo-meta currently writes nothing. After a manual change, wp-agent-get-seo-meta reads the stored state, while render-verify checks the visible output.
3. Google never hears about the new post (hook gaps around sitemap and cache)
What you see
After creating a post, the sitemap stays outdated, new permalinks don't work right away, or the post still shows an old state in caches. (The sitemap is the internal list that shows search engines your pages; a cache is a temporary store that serves pages faster but sometimes shows stale content.)
Why it happens
WordPress does certain follow-up work only when you click "Save" in the editor, not when a program creates the post via the technical interface. That follow-up work otherwise falls through silently.
What's behind it
wp-agent/refresh-hooks (packages/wp-agent-companion/includes/abilities/refresh-hooks.php) explicitly catches up on three things:
flush_rewrite_rules(false): rebuilds the permalink rules without writing.htaccess.clean_post_cache()for the most recently changed posts (default: the last 20, controllable vialimitorpost_ids).- If Rank Math is active (same detection as
set-seo-meta): clears the sitemap cache. Preferred viaRankMath\Sitemap\Cache::invalidate_storage(), withRankMath\Sitemap\Cache_Watcher::clear()as a fallback. If wpagent finds neither method, only the documented WP-CLI fallbackwp rankmath sitemap generateremains, which it does not run itself.
Both processes (article.mjs, landing.mjs) call refresh-hooks automatically only after a remote content mutation has passed their earlier gates. If a required later writer is manual-only or its write semantics are not declared, the process fails closed before that mutation and no refresh step is reached.
Fix
If you're running via the ready-made processes (wp-agent article, wp-agent landing), this step is already automatic; you don't need to do anything extra. If you're driving the tools individually through your assistant (i.e. without the ready-made processes), have wp-agent/refresh-hooks called after every write. Check the rankmath.method field in the result: if it's null while rankmath.active:true, no cache method was found, and only the WP-CLI fallback wp rankmath sitemap generate helps, if you have WP-CLI access.
4. Images open a security hole, or alt text and featured image are missing
What you see
An image download aborts with an "SSRF" error message even though the address looks harmless. Or: the image was uploaded but isn't attached to the post, or has no alt text.
Why it happens (case A: SSRF protection)
When an AI picks the address it downloads an image from itself, that's an open door for attackers without safeguards. That's exactly what the SSRF protection prevents (SSRF stands for "Server-Side Request Forgery," roughly: tricking the site into secretly fetching internal addresses). WPAgently checks every image address before it fetches it:
- only
http/httpsare allowed, nofile://or other schemes, localhostand*.localhostare blocked,- every IP address is checked, direct IPs immediately, names only after resolution, and this for EVERY associated address, not just the first,
- private and internal address ranges are rejected (this includes the cloud metadata address 169.254.169.254),
- every redirect is fetched individually and fully re-checked before the next step, up to a maximum of 3 redirects. This protects against a trick where an address looks harmless on the first check but is redirected to an internal one on the actual fetch.
Typical messages from this check include:
SSRF protection: only http and https addresses are allowed ...SSRF protection: this address points to the system itself (localhost) ...SSRF protection: this address leads into a private or local network ...SSRF protection: the host ... cannot be resolved ...
Known, harmless edge case: if the image address has no recognizable file extension (for example a hashed-looking CDN address), WordPress can't determine the file type and rejects the upload, even though the image is fine. Fix: use an image address with a recognizable extension (.jpg, .png, .webp, and so on).
Why it happens (case B: missing alt text or featured image)
The alt text (the image description for search engines and screen readers) is required on image upload; an empty text is rejected. After the upload, wpagent writes the alt text and immediately reads it back. Setting it as the featured image goes through the official WordPress function and is read back as well.
Where the process aborts
The featured image requires alt text.
Aborted: the featured image or alt text could not be confirmed.
Fix
Only use public http(s) addresses or local file paths as an image source, never internal names or IP addresses. Always supply a descriptive alt text that describes the image (not just the keyword). If an already-uploaded image has no alt text, you can fix it afterward with wp-agent/set-alt-text and the attachment_id (an empty text is deliberately allowed there, in case you want to intentionally clear a faulty alt text again).
5. The same article lands on your site twice
What you see
After several runs with the same or similar inputs, two or more posts exist with the same or a very similar slug. (The slug is the part of the URL that names the post, for example my-article in your-site.com/my-article/.)
Why it happens
Without a check before creating, a second run with the same data simply creates a second, independent post instead of updating the first.
What's behind it
Before every insert, the process searches for an existing match via findPostBySlug() (packages/wp-agent/src/safety/idempotency.mjs, for articles) or findPageBySlug() (packages/wp-agent/src/pipelines/landing.mjs, for landing pages). The search also finds drafts, not just published posts. Only an empty, successful response permits a new resource. Exactly one exact slug match may become the existing target. Multiple exact matches, only non-exact results, invalid IDs, or a failed response are ambiguous and abort before any mutation without selecting a post. For articles, one exact existing match becomes an update plan. The current create-post-from-markdown update mode is manual_only, so the pipeline fails closed before creating a replacement draft. Landing pages use the separate upsert-pattern update path and its declared update semantics.
Important: this search runs exclusively via the slug. Without a supplied slug, no check happens; every run creates a new post (with a slug WordPress generates automatically from the title).
Fix
Pass a stable, explicit --slug on every run, not just the title. If the slug changes between two runs (for example because the title changed and no slug was set), the process doesn't recognize the old post and plans a new article. Existing article updates must currently be completed manually in WordPress. Passing post_id to create-post-from-markdown or calling update-post returns HTTP 409 with manual_only and performs no remote update.
Part 2: special cases of individual features
These points are not among the five core failures, but concrete special cases you'll only run into if you use exactly those features.
2.1 WooCommerce products: only via the official path, never via raw writes
What you see
A price was changed, the new value is in the database, but sorting or filtering by price in the shop still shows the old state.
Why it happens
wp-agent/update-product (packages/wp-agent-companion/includes/abilities/update-product.php) documents the actual pitfall, and it's not the common HPOS claim: High-Performance Order Storage affects only orders, not products. The real pitfall is a different one: a raw write to the price field does change the stored value, but neither WooCommerce's own cache nor the separate lookup table (wp_wc_product_meta_lookup) that the shop uses for price sorting and filtering. The price then "looks right" in the database, but sorting and filtering stay on the old state, with no error message at all.
How WPAgently solves it
update-product writes exclusively via WooCommerce's official setters and then calls save(). Only save() updates the cache and the lookup table consistently. To be sure, wpagent then reads the product back fresh afterward, which is the actual proof that the values landed via the full WooCommerce path.
Second pitfall: duplicate SKU
A SKU is a product's article number; it has to be unique. If it's already taken, WooCommerce would crash with a hard error (HTTP 500) without precautions. update-product therefore checks beforehand whether the SKU already exists, and then returns a clean message with the colliding product ID: "The SKU [...] is already used by product #...".
Third pitfall: permission
WooCommerce allows editing products by default only for Shop Managers and Administrators, not for the Editor role. The usual Editor bot therefore gets a regular "no permission" (403) when it tries, even though it writes normal posts without issue. This is not a bug, but WooCommerce's own permission model.
Fix
Use exclusively wp-agent/update-product for product changes. For an SKU collision, check the reported product ID and choose a unique SKU. If the bot needs write access to products, it needs to be granted the WooCommerce permission edit_products (or the Shop Manager role) in addition to the Editor role. You decide that in WordPress itself, outside of WPAgently.
2.1a WooCommerce orders: visible but blocked with 403
What you see
The five order abilities are visible in Full or Read-only, but the agent receives HTTP 403 when calling them. Alternatively, they are absent while the Content profile is active.
Why it happens
Orders can contain names, addresses, contact details, transaction identifiers, and confidential notes. read_private_shop_orders and edit_shop_orders are therefore separated from ordinary product permissions and are never granted to an Editor automatically. The order group is also deliberately excluded from the recommended Content profile.
Fix
Open WPAgently > Connect agent. In “WooCommerce orders for the agent user,” select the dedicated Editor and explicitly enable order permissions. Then choose Full or Custom when status and notes must be edited. Read-only exposes only the three read tools. Request customer, payment, or line-item data through the respective include_* switch only when the specific task needs it. Status transitions require confirm: true, and customer notes additionally require confirm_customer_notification: true. Refunds, order creation, line-item changes, and payment operations are not supported.
2.2 ACF fields: the field_key is required, the field name is not enough
What you see
A call to update-acf-field with the field name (for example mein_feld instead of field_65abc123) fails, or the field is not updated as expected. (ACF, Advanced Custom Fields, is a very widely used plugin for custom extra fields on posts and pages.)
Why it happens
ACF technically also accepts the field name but then tries to resolve it itself. That is ACF's own fallback, not the recommended addressing: it becomes ambiguous as soon as two field groups have a field with the same name, and it fundamentally doesn't work for sub-fields of repeater, flexible content, or group fields. That's why WPAgently requires the unambiguous internal key (field_key), which always starts with field_.
How WPAgently solves it
wp-agent/update-acf-field (packages/wp-agent-companion/includes/abilities/update-acf-field.php) accepts only the real field_ key of a top-level field currently assigned to the target object. Posts, users, terms, comments, and ACF options storage are supported targets. Before writing, WPAgently checks the object-specific edit permission, validates the value recursively against the field definition, requires a current state hash, and acquires a short-lived lock. It then reads the unformatted value again. If the value differs, the path restores the previous state and verifies the rollback as well.
How to get the field_key
wp-agent/get-acf-fields returns all ACF fields assigned to the target object, including their field_ key, name, label, type, portable raw value, canonical acf_object_id, state_hash, writability, and redacted-reference count. Posts, media, or users without read permission are not disclosed. Field types that cannot be represented safely, unknown flexible-content layouts, oversized existing values, and fields missing an additional required permission remain readable but not writable.
Fix
Before every update-acf-field call, first call get-acf-fields for the same target. { "post_id": 42 } remains compatible for posts. Other targets require object_type and object_id together, for example { "object_type": "user", "object_id": 7 }. The generic options store uses { "object_type": "options", "object_id": "options" }. Pass the key and state_hash from the same field response unchanged as field_key and expected_hash. On HTTP 409, read the field again instead of blindly retrying the same write. Date fields use ACF's unformatted storage representation, so date_picker uses YYYYMMDD. Options-page definitions are outside this value path and remain unavailable without a matching ACF runtime.
2.2a ACF content model: definition is not portable or runtime slug is occupied
Symptom
update-acf-post-type or update-acf-taxonomy returns HTTP 409. The previous read response contains portable: false or registration_conflict: true. Deletion may instead require an additional confirmation for existing content or terms.
Cause
The bounded write path can represent only the properties listed in the ability reference without data loss. Further ACF settings are not silently removed. registration_conflict: true means that a theme, plugin, or PHP definition already registered the same post-type or taxonomy slug in WordPress. Deleting the ACF definition deliberately does not remove posts or terms.
Solution
Reread the definition immediately before a write with get-acf-post-type or get-acf-taxonomy, then pass the current hash as expected_hash. Change nonportable or conflicting definitions in ACF or in the component that owns the registration. Set confirm_content=true only when the definition should deliberately disappear while its content or terms remain hidden in the database.
2.3 Contact Form 7: the normal content field has no effect here
What you see
A write attempt on a Contact Form 7 form appears to "succeed" without anything actually changing on the form itself.
Why it happens
Contact Form 7 stores the actual form configuration in its own field (_form), not in the normal content field (post_content). A write to post_content verifies cleanly (the value really is in the database), but has no visible effect, because Contact Form 7 doesn't read that field at all when displaying the form.
Two safe paths
First layer: create-post-from-markdown allows only public post types. The type used by Contact Form 7 is not public, so an attempt is rejected with "Post type wpcf7_contact_form is not public and is not accessible via this ability."
Second path: Use only wp-agent/list-contact-forms, get-contact-form, create-contact-form, update-contact-form, duplicate-contact-form, and delete-contact-form for forms. These Abilities use Contact Form 7's own object, sanitizer, and validation APIs and read the result back after saving. render-verify still warns with cf7_post_content_inert if a generic content write misses the real form storage.
Fix
Use the six native form Abilities or the regular Contact Form 7 UI. Never write through update-post or directly into _form. Permanent deletion additionally requires confirm: true. If forms should be read-only, disable the four write Abilities in WPAgently's custom profile.
2.4 Gravity Forms: the Editor has no form permissions
What you see
The Gravity Forms Abilities are registered, but the agent user is denied access to them.
Why it happens
Gravity Forms uses gravityforms_edit_forms, gravityforms_create_form, and gravityforms_delete_forms for forms. Entries separately use gravityforms_view_entries, gravityforms_edit_entries, and gravityforms_delete_entries. A regular WordPress Editor does not receive them automatically. Making the agent user an Administrator would be unnecessary and would remove the connection's safety boundary.
Fix
Open WPAgently > Connect agent. In the “Gravity Forms for the agent user” section, select the dedicated Editor and click “Enable Gravity Forms permissions” for forms. Enable entry permissions separately below it. This second control explicitly warns about personal and payment data and is never activated automatically when an agent user is created. WPAgently records only capabilities it actually adds. Removal and plugin uninstallation remove only those additions. Existing role permissions remain intact.
Then use the six gravity-form tools for forms and the five gravity-entry tools for entries. list-gravity-entries returns sensitive detail values only with include_values: true. Permanent deletion requires confirm: true. Payment mutations, new submissions, notes, transactions, and unknown add-on metadata remain outside this integration.
2.5 WPForms: native abilities are visible but blocked for the Editor
What you see
Native WPForms abilities are registered, but the safe agent user receives HTTP 403 when calling them. Write tools may also be absent entirely. WPForms Lite exposes no entry abilities.
Why it happens
WPForms restricts its native abilities to Administrators by default. WPAgently therefore does not make the agent user an Administrator. It provides three separate permissions for form reads, sensitive entry data, and form changes. These additions apply exclusively at WPAgently's MCP endpoint. They do not open the normal WPForms admin, foreign REST routes, or AJAX surfaces. WPForms' own write switch remains a separate additional gate. Abilities that WPForms does not publish for MCP remain closed.
Fix
Open WPAgently > Connect agent and enable only the required WPForms permission group for the dedicated Editor. Entry access is never granted automatically. IP addresses in native entry responses are masked for this user. For write access, additionally enable the WPForms write switch under WPForms > Tools > AI MCP, then enable the native write group in WPAgently's control center. If WPForms is later deactivated, grants tracked by WPAgently remain visible and revocable on the connection page. On uninstallation, WPAgently removes only permissions it added itself.
2.6 Ninja Forms: safely constrain the provider's broad permission
What you see
Native Ninja Forms tools are visible, but the Editor receives HTTP 403. Some deletion, settings, and submission tools remain absent even after enabling access.
Why it happens
Ninja Forms protects its native abilities with the broad nf_edit_forms permission. Persisting that permission on an Editor would open harmless reads, personal-data submissions, form changes, and email delivery at the same time. WPAgently therefore replaces the broad persistent grant with four revocable permissions. Eight risky provider paths remain quarantined. They cover deletion of forms, fields, actions, calculations, and submissions, global plugin settings, and unverified submission changes.
Fix
Open WPAgently > Connect agent and enable only the group you need. Form reading is prepared for a newly created agent user. Submissions, writes, and email delivery stay off until you approve each group. process-submission can send real emails and therefore also requires the dedicated delivery group. The nf_edit_forms permission is mediated only during a matching, fully checked call at WPAgently's MCP endpoint. It is never stored on the user account. Mixed JSON-RPC batches and batches with more than 100 messages are rejected.
2.7 Page builder dataset present, but the plugin is not active
What you see
render-check reports a valid state, but the page looks different on the frontend than expected, or like an empty/raw version. (A page builder like Elementor, Bricks, Breakdance, or Oxygen builds pages in its own interface and stores them in its own format, not as normal WordPress blocks.)
Why it happens
These builders store their content in their own hidden fields, not in the normal content field. If such a dataset exists but the associated plugin is not active (deactivated, deleted, or never installed), the frontend shows only the raw content field, never the builder output. Whether a plugin is active says nothing on its own about how a specific post is stored.
What's behind it
wp-agent/detect-builder (packages/wp-agent-companion/includes/abilities/detect-builder.php) detects, per post, from the actual storage format, what the content is stored with. wp-agent/render-verify additionally checks whether the detected builder is actually loaded. The check is called builder_render_mismatch; agrees:false with severity:warn means: a builder dataset exists, the plugin is not active, and the frontend shows only raw content.
Fix
Before editing, call wp-agent/detect-builder on the target post and check the primary and storage_location fields. If storage_location shows "postmeta" or "mixed", builder data lives outside the normal content field; changes via create-post-from-markdown/update-post then have no visible effect as long as the builder plugin is not active. After every change, run wp-agent/render-verify and check that the builder_render_mismatch check reports agrees:true before considering the post done.
2.8 SEO read-back trap with Yoast (since version 14)
What you see
After a manual SEO change, the Google search result or the Yoast preview still shows the old title or the old description.
Why it happens
Rank Math reads title and description on display from the same fields it writes to, so a divergence is impossible there. Yoast, however, displays from its own extra table (wp_yoast_indexable) since version 14, not directly from the meta fields. A freshly written field looks clean on a pure field check, while the frontend still shows the old values from the extra table, because Yoast doesn't rebuild that table immediately on every programmatic write.
What's behind it
This trap does not affect set-seo-meta as a remote write: it writes nothing. The trap lies in the downstream display after a manual change, which is why wp-agent/render-verify checks it separately in the seo_read_back check. With Yoast active, it reads the effective presentation through YoastSEO()->meta->for_post(). In production, this Meta Surface accounts for the persisted Indexable. In local environments, it also handles Yoast's deliberately in-memory presentation. If the effective output differs from the stored values after Yoast replacement variables are resolved, the check reports severity:warn. Rank Math and SEOPress are checked through their provider-owned meta fields, and AIOSEO through its native SEO Ability.
The Yoast path runs in the practical MySQL compatibility matrix against a real active installation. AIOSEO requires MariaDB for its own database layer, and genuine AIOSEO runtime coverage is MariaDB-only. The plugin does not activate reliably with the official SQLite integration, so AIOSEO is not claimed as SQLite-compatible.
Fix
After every manual Yoast change, call wp-agent/get-seo-meta and wp-agent/render-verify, then inspect seo_read_back. If agrees:false remains, run Yoast's reindexing tool under Yoast SEO > Tools and verify the post again. set-seo-meta does not trigger Yoast's save path.
2.9 Builder caches after an edit (separate from point 3)
What you see
After editing a post with Spectra, GenerateBlocks, or WooCommerce product content, the frontend still shows old presentations or outdated product data (price filters, related products), even though render-check correctly confirms the new content.
Why it happens
Some builders maintain their own cache that isn't automatically cleared on a programmatic write, independent of the sitemap problem from point 3.
What's behind it
wp-agent/refresh-builder-cache (packages/wp-agent-companion/includes/abilities/refresh-builder-cache.php) triggers the matching official clearing per detected builder. Empirically checked against the installed plugin versions:
- Spectra 2.20.1: update the asset version plus a targeted deletion of the page assets (like the official
wp spectra regenerate-csscommand). - GenerateBlocks 2.3.0: the same method the plugin itself calls on save.
- WooCommerce 10.9.4:
wc_delete_product_transients(), deletes global and product-specific caches. - Kadence Blocks 3.7.8 and WPBakery: self-healing on display, no action needed.
- Elementor 4.2.1: practically verified. The native document path uses
Document::save(), which removes post CSS and document cache, then confirms the result through a complete read-back and frontend render. Responsive element styles are additionally derived from the public control and breakpoint registries, strictly validated by control type, then compiled and verified through Elementor's public post CSS class. The general cache wrapper additionally uses the loaded officialfiles_manager->clear_cache()API. - Breakdance and Oxygen: use a loaded in-request interface where available. Without a licensed test artifact, these paths remain defensive and are not practically certified.
- Bricks: not included in the cache list in the underlying blueprint, so no action [to clarify: whether Bricks needs its own cache mechanism is left open in the code].
Fix
After edits to posts with Spectra, GenerateBlocks, or WooCommerce product content, also call wp-agent/refresh-builder-cache with the post_id, in addition to refresh-hooks (point 3). Unlike refresh-hooks, this step is currently NOT automatically part of the article or landing processes [to clarify: whether refresh-builder-cache will be automatically included in the processes in the future]. Check the verified field per action in the result; if it says false while executed:true, the clearing could be triggered but not confirmed.
Part 3: self-diagnosis tools
These tools change nothing, they only show (all are readonly:true). They are the right first step before filing a support request. You call them through your assistant (Claude Code/Codex); the tool name is the ability name with a hyphen instead of a slash, for example wp-agent-render-verify.
| Tool | Shows |
|---|---|
wp-agent/render-check | Block count, core/freeform count, rendered HTML, word count for a post |
wp-agent/render-verify | The actual frontend output plus four trap checks: freeform, builder mismatch, CF7 trap, SEO read-back |
wp-agent/detect-builder | Which page builder/format a post actually uses, with all found signals and their storage location |
wp-agent/get-acf-fields | All ACF fields assigned to an authorized post, user, term, comment, or options store, with field_ key, portable raw value, object ID, state hash, writability, and reference redaction |
wp-agent/list-media | Existing media files [to clarify: exact filter/sort options not substantiated from the code read] |
wp-agent/get-post | Current state of a post including raw content [to clarify: exact feature scope not part of this analysis] |
wp-agent doctor, wp-agent preflight, and wp-agent abilities (CLI commands, not an assistant tool), by contrast, check the connection itself, not individual content; see docs/en/setup.md, section "Diagnostics".
Appendix: error message patterns
For quick recognition in logs or assistant output. These phrasings come from the code; N, <plugin>, and ellipses stand for variable parts here.
| Error message | Source | Means |
|---|---|---|
| "Aborted: N freeform blocks appeared during conversion. This prevents silently falling back to the old Classic editor." | article.mjs | Failure 1 |
| "Aborted: N freeform blocks appeared while building the landing page. This prevents silently falling back to the old Classic editor." | landing.mjs | Failure 1 |
| "Aborted: the render check reports N freeform blocks." | article.mjs, landing.mjs | Failure 1, second check |
| "Aborted: the SEO details were not saved (plugin <plugin>)." | article.mjs | Failure 2 |
| "Aborted: the featured image or alt text could not be confirmed." | article.mjs | Failure 4 |
| "The featured image requires alt text." | media.mjs | Failure 4, missing alt text before upload |
| "SSRF protection: only http and https addresses are allowed …" | ssrf.mjs | Failure 4, disallowed URL scheme before CLI download |
| "SSRF protection: this address points to the system itself (localhost) …" | ssrf.mjs | Failure 4, local host before CLI download |
| "SSRF protection: this address leads into a private or local network …" | ssrf.mjs | Failure 4, private address before CLI download |
| "SSRF protection: the host … cannot be resolved …" | ssrf.mjs | Failure 4, DNS failure before CLI download |
| "SSRF protection: only http/https allowed …" | upload-media.php | Failure 4, disallowed URL scheme in the direct WordPress upload |
| "SSRF protection: localhost/loopback is blocked …" | upload-media.php | Failure 4, internal hostname |
| "SSRF protection: target IP … is in a private/loopback/link-local range …" | upload-media.php | Failure 4, private IP after DNS resolution |
| "SSRF protection: host … not resolvable, download refused." | upload-media.php | Failure 4, DNS failure, fail-closed |
"The SKU <SKU> is already used by product #…" | update-product.php | 2.1, WooCommerce SKU collision |
"field_key must be the ACF field key (starts with field_), not the field name." | update-acf-field.php | 2.2, ACF field name instead of key |
"Post type wpcf7_contact_form is not public and is not accessible via this ability." | helpers.php (wpagent_validate_post_type) | 2.3, CF7 blocked via create-post-from-markdown |
"post_content does not render as a form for CF7; the configuration lives in postmeta _form." | render-verify.php | 2.3, CF7 trap detected |