Technischer Prüfstand: 24.08.2026. Die Produktabläufe und Grenzen sind gegen Companion 0.4.117, Power 0.6.35 sowie CLI und Skills 0.4.86 geprüft. Angaben zu Drittanbietern können sich unabhängig davon ändern.
Dieses Dokument ist für dich gedacht, wenn deine Verbindung schon steht (wp-agent doctor meldet alle sieben Checks OK, siehe Schnellstart, Abschnitt „Diagnose“). Es beantwortet eine ganz bestimmte Frage: Der Vorgang läuft durch, aber am Ergebnis stimmt trotzdem etwas nicht, ein Beitrag ist leer geblieben oder doppelt entstanden. Warum, und wie behebe ich das?
Zuerst die gute Nachricht: In fast allen Fällen unten ist der Abbruch, den du siehst, kein Bug, sondern WPAgently, das dich vor einem stillen Schaden bewahrt. Du musst nichts reparieren, was kaputt ist, du musst nur die Eingabe leicht anpassen und es noch einmal laufen lassen. Jeder Abschnitt sagt dir genau, was zu tun ist.
Geht es dagegen um ein Verbindungsproblem (Seite nicht erreichbar, OAuth-Zugang widerrufen, Application Password ungültig, Bot in der falschen Rolle, MCP-Server antwortet nicht), ist wp-agent doctor der richtige erste Schritt, nicht dieses Dokument. Wenn Doctor und Client unterschiedliche Ergebnisse liefern, starte danach unter WPAgently > Agent verbinden die externe Diagnose. Sie prüft den öffentlichen Weg durch CDN, Firewall und Security-Plugin ohne Zugangsdaten.
Wie du dieses Dokument liest
Teil 1 behandelt die fünf stillen Fehler, die WPAgently für dich abfängt. Zu jedem steht hier: Was du siehst, warum es passiert, was WPAgently im Hintergrund dagegen tut, und mit welchem genauen Wortlaut der Vorgang abbricht, wenn du ihn triffst. Die „Was dahinter steckt“-Absätze sind für Neugierige. Wenn dir Symptom und Abhilfe reichen, kannst du sie überspringen.
Teil 2 behandelt zusätzliche Sonderfälle einzelner Funktionen: WooCommerce-Produkte und Bestellungen, ACF-Felder, Contact Form 7, Gravity Forms und die Erkennung von Page-Buildern. Das sind keine der fünf Kernfehler, sondern konkrete Einzelfälle, die dir nur begegnen, wenn du genau diese Funktionen nutzt.
Teil 3 listet die nur lesenden Werkzeuge auf, mit denen du selbst nachsehen kannst, was auf einem Beitrag tatsächlich gespeichert ist, bevor du den Support kontaktierst.
Teil 1: Die fünf stillen Fehler
Versionskopplung der kombinierten Pipelines
Die Artikel- und Landing-Pipelines prüfen vor der ersten Remote-Mutation, ob der verbundene Companion die flachen Schreibsemantiken remote_create und manual_only_update sowie das allgemeine manual_only deklariert. Fehlen diese Metadaten oder sind sie ungültig, bricht die CLI fail-closed ab und meldet, dass kein Inhalt geändert wurde. Das betrifft bestehende Markdown-Updates sowie angeforderte SEO- und Status-Schritte. Ein älterer Companion ist für diese kombinierten Pipelines deshalb nicht voraussetzungslos kompatibel. Einzelne Abilities bleiben nach ihrem installierten Schema nutzbar. Siehe auch Kompatibilitätsmatrix.
1. Der Artikel steht plötzlich unformatiert da (core/freeform)
Was du siehst
Der Beitrag oder die Landing-Page erscheint im Editor als ein einziger grauer „Classic“-Block statt als saubere, einzeln bearbeitbare Blöcke. In der Antwort steht freeform_count größer als 0.
Zur Einordnung: Der moderne WordPress-Editor (Gutenberg) baut Inhalte aus einzelnen „Blöcken“ auf, ein Block pro Überschrift, Absatz, Bild und so weiter. Der „Classic“-Block ist der Notnagel für alten, unformatierten Inhalt. Landet dein ganzer Artikel in so einem grauen Block, ist die Formatierung verloren.
Der Assistent hat Inhalt geliefert, den WordPress nicht als saubere Blöcke lesen konnte (zum Beispiel weil direkt HTML statt Markdown geschrieben wurde). WordPress wirft dann keinen Fehler, sondern legt den kompletten Inhalt still als einen core/freeform-Block ab. Genau dieses stille Umschalten ist die Falle. (Markdown ist eine einfache Textauszeichnung mit **Fett**, ## Überschrift und Listen; daraus baut WPAgently die Blöcke serverseitig.)
Was dahinter steckt
wpagent_block_report() (packages/wp-agent-companion/includes/helpers.php) zählt für jeden geparsten Block, ob blockName null ist und innerHTML nicht leer: genau das ist die Freeform-Signatur. Diese Prüfung wird an vier Stellen wiederverwendet: create-post-from-markdown, update-post, render-check und render-verify. Die verifizierte ARTIKEL-Pipeline legt neue Artikelinhalte ausschließlich über wp-agent/create-post-from-markdown an. Landing-Pages verwenden wp-agent/upsert-pattern. Beide Pfade erzeugen gültige Blöcke serverseitig, statt dass der Assistent selbst Block-Code formuliert.
Wo der Vorgang abbricht
Die Artikel- und Landing-Prüfung (packages/wp-agent/src/pipelines/article.mjs, landing.mjs) kontrolliert freeform_count zweimal: direkt nach der Umwandlung und noch einmal über wp-agent/render-verify. Der Ergebnis-Schritt heißt intern render-check. Bei einem Treffer bricht sie mit einem dieser Muster ab:
Abgebrochen: Bei der Umwandlung sind <N> Freiform-Blöcke entstanden. So wird verhindert, dass unbemerkt der alte Classic-Editor verwendet wird.
Abgebrochen: Beim Aufbau der Landingpage sind <N> Freiform-Blöcke entstanden. So wird verhindert, dass unbemerkt der alte Classic-Editor verwendet wird.
Abgebrochen: Die Darstellungsprüfung meldet <N> Freiform-Blöcke.
Abhilfe
Lass den Assistenten ausschließlich über create-post-from-markdown beziehungsweise upsert-pattern schreiben, nie direkt HTML oder Block-Code. Wenn ein bestimmter Markdown-Inhalt Probleme macht (zum Beispiel komplexe verschachtelte Listen oder ungewöhnliche Tabellen, die die Umwandlung nicht kennt), lass ihn vereinfachen und noch einmal laufen. Der Abbruch selbst ist Absicht: Er verhindert, dass ein kaputt formatierter Beitrag unbemerkt als Entwurf liegen bleibt oder sogar online geht.
2. Deine SEO-Änderung bleibt aus (der aktuelle SEO-Writer ist manual-only)
Was du siehst
set-seo-meta liefert HTTP 409 mit manual_only, und Titel, Beschreibung oder Fokus-Keyword bleiben unverändert. Das ist derzeit die erwartete Antwort, kein stiller Erfolg.
Warum es passiert
SEO-Angaben werden nicht als normaler Text gespeichert, sondern in speziellen versteckten Feldern (sogenannten Meta-Feldern, im Klartext: Zusatzinfos, die an einen Beitrag hängen, aber nicht im Text stehen). Jedes SEO-Plugin nutzt dafür eigene Feldnamen. Ein vollständiger atomarer Compare-and-Write-Nachweis über die gesamte Providerfläche fehlt derzeit, deshalb nimmt WPAgently keine Remote-Mutation vor.
Was dahinter steckt
wp-agent/get-seo-meta (packages/wp-agent-companion/includes/abilities/get-seo-meta.php) erkennt über wpagent_detect_seo_plugin(), welches unterstützte Plugin aktiv ist, und liest die wirksamen Providerfelder. Rank Math und Yoast nutzen ihre wirksamen Metafelder. AIOSEO wird über seine eigene WordPress-Ability gelesen. SEOPress wird über seine nativen REST-Routen gelesen. set-seo-meta validiert dieselbe Eingabe und liest den Zustand frisch, endet dann aber providerübergreifend mit HTTP 409 und manual_only, bevor ein Feld geschrieben wird.
| Plugin | Titel | Description | Fokus-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 Ability aioseo-posts/seo-data-update | dieselbe Ability | dieselbe Ability |
| SEOPress | native REST-Route title-description-metas | dieselbe Route | native REST-Route target-keywords |
Der frische Re-Read und der Konflikthash schützen davor, einen veralteten Zustand als Grundlage für eine manuelle Änderung zu übersehen. Ohne unterstütztes aktives Plugin bleibt der Pfad ebenfalls geschlossen. Es werden bewusst keine eigenen Ersatzfelder gespeichert, weil das aktive SEO-System diese nicht ausgeben würde. Der AIOSEO-Lesepfad ist praktisch nur auf MariaDB nachgewiesen; MySQL und SQLite gehören nicht zu diesem Laufzeitnachweis.
Wo der Vorgang abbricht
Abgebrochen: Die SEO-Angaben wurden nicht gespeichert (Plugin <plugin>).
Abhilfe
Prüfe bei HTTP 409 den Fehlercode. wpagent_seo_plugin_missing verlangt ein aktives Rank Math, Yoast, AIOSEO oder SEOPress. Bei manual_only änderst du die SEO-Angaben im jeweiligen Plugin-Backend und liest sie anschließend mit wp-agent-get-seo-meta erneut. Wiederhole den set-seo-meta-Aufruf nicht, solange der Provider keinen nachweisbaren vollständigen Compare-and-Write-Pfad anbietet. Nutze danach wp-agent-render-verify, wenn du zusätzlich die sichtbare Ausgabe prüfen musst.
Wichtig für Yoast: siehe Punkt 2.5 in Teil 2, „SEO-Read-back-Falle bei Yoast“. Der aktuelle set-seo-meta-Writer schreibt nicht. Nach einer manuellen Änderung liest wp-agent-get-seo-meta den Zustand erneut, während render-verify die sichtbare Ausgabe prüft.
3. Google erfährt nichts vom neuen Beitrag (Hook-Lücken bei Sitemap und Cache)
Was du siehst
Nach dem Anlegen eines Beitrags bleibt die Sitemap veraltet, neue Permalinks funktionieren nicht sofort, oder der Beitrag taucht in Zwischenspeichern (Caches) noch mit altem Stand auf. (Die Sitemap ist die interne Liste, die Suchmaschinen deine Seiten anzeigt; ein Cache ist ein Zwischenspeicher, der Seiten schneller ausliefert, aber manchmal Veraltetes zeigt.)
Warum es passiert
WordPress erledigt bestimmte Nacharbeiten nur, wenn du im Editor auf „Speichern“ klickst, nicht wenn ein Programm den Beitrag über die technische Schnittstelle anlegt. Diese Nacharbeiten fallen dann sonst still aus.
Was dahinter steckt
wp-agent/refresh-hooks (packages/wp-agent-companion/includes/abilities/refresh-hooks.php) holt drei Dinge ausdrücklich nach:
flush_rewrite_rules(false): baut die Permalink-Regeln neu auf, ohne die.htaccesszu schreiben.clean_post_cache()für die zuletzt geänderten Beiträge (Standard: die letzten 20, perlimitoderpost_idssteuerbar).- Falls Rank Math aktiv ist (dieselbe Erkennung wie bei
set-seo-meta): den Sitemap-Cache leeren. Bevorzugt überRankMath\Sitemap\Cache::invalidate_storage(), als FallbackRankMath\Sitemap\Cache_Watcher::clear(). Findet wpagent keine der beiden Methoden, bleibt nur der dokumentierte WP-CLI-Fallbackwp rankmath sitemap generate, den es selbst nicht ausführt.
Beide Abläufe (article.mjs, landing.mjs) rufen refresh-hooks nur dann automatisch als letzten Schritt auf, wenn eine Remote-Content-Mutation die vorherigen Gates passiert hat. Ist ein späterer Writer manual-only oder fehlt seine deklarierte Schreibsemantik, bricht der Ablauf vorher ab und erreicht den Refresh-Schritt nicht.
Abhilfe
Läufst du über die fertigen Abläufe (wp-agent article, wp-agent landing), ist dieser Schritt schon automatisch dabei, du musst nichts extra tun. Steuerst du die Werkzeuge einzeln über deinen Assistenten an (also ohne die fertigen Abläufe), lass wp-agent/refresh-hooks nach jedem Schreiben aufrufen. Prüfe im Ergebnis das Feld rankmath.method: steht dort null bei rankmath.active:true, wurde keine Cache-Methode gefunden, dann hilft nur der WP-CLI-Fallback wp rankmath sitemap generate, falls du WP-CLI-Zugriff hast.
4. Bilder öffnen ein Sicherheitsloch, oder Alt-Text und Beitragsbild fehlen
Was du siehst
Ein Bild-Download bricht mit einer „SSRF“-Fehlermeldung ab, obwohl die Adresse harmlos aussieht. Oder: das Bild wurde hochgeladen, hängt aber nicht am Beitrag, oder hat keinen Alt-Text.
Warum es passiert (Fall A: SSRF-Schutz)
Wenn eine KI selbst aussucht, von welcher Adresse sie ein Bild lädt, ist das ohne Absicherung ein Einfallstor für Angreifer. Genau das verhindert der SSRF-Schutz (SSRF steht für „Server-Side Request Forgery“, sinngemäß: die Seite dazu bringen, heimlich interne Adressen abzurufen). WPAgently prüft jede Bild-Adresse, bevor es sie lädt:
- nur
http/httpssind erlaubt, keinfile://oder andere Schemata, localhostund*.localhostsind blockiert,- jede IP-Adresse wird geprüft, direkte IPs sofort, Namen erst nach Auflösung, und zwar für JEDE zugehörige Adresse, nicht nur die erste,
- private und interne Adressbereiche werden abgelehnt (das schließt die Cloud-Metadaten-Adresse 169.254.169.254 mit ein),
- jede Weiterleitung wird einzeln geholt und vor dem nächsten Schritt erneut komplett geprüft, maximal 3 Weiterleitungen. Das schützt gegen einen Trick, bei dem eine Adresse bei der ersten Prüfung harmlos aussieht, beim tatsächlichen Laden aber auf eine interne umgelenkt wird.
Typische Meldungen dieser Prüfung sind zum Beispiel:
SSRF-Schutz: Erlaubt sind nur http- und https-Adressen ...SSRF-Schutz: Diese Adresse zeigt auf das eigene System (localhost) ...SSRF-Schutz: Diese Adresse führt in ein privates oder lokales Netz ...SSRF-Schutz: Der Host ... lässt sich nicht auflösen ...
Bekannter, harmloser Randfall: Hat die Bild-Adresse keine erkennbare Dateiendung (etwa eine verschlüsselt aussehende CDN-Adresse), kann WordPress den Dateityp nicht zuordnen und lehnt den Upload ab, obwohl das Bild in Ordnung ist. Abhilfe: eine Bild-Adresse mit erkennbarer Endung nutzen (.jpg, .png, .webp und so weiter).
Warum es passiert (Fall B: Alt-Text oder Beitragsbild fehlt)
Der Alt-Text (die Bildbeschreibung für Suchmaschinen und Screenreader) ist beim Bild-Upload Pflicht; ein leerer Text wird abgelehnt. Nach dem Upload schreibt wpagent den Alt-Text und liest ihn sofort zurück. Das Setzen als Beitragsbild läuft über die offizielle WordPress-Funktion und wird ebenfalls zurückgelesen.
Wo der Vorgang abbricht
Für das Beitragsbild ist ein Alt-Text Pflicht.
Abgebrochen: Das Beitragsbild oder der Alt-Text konnte nicht bestätigt werden.
Abhilfe
Nur öffentliche http(s)-Adressen oder lokale Dateipfade als Bildquelle verwenden, nie interne Namen oder IP-Adressen. Immer einen beschreibenden Alt-Text mitgeben, der das Bild beschreibt (nicht bloß das Keyword). Hat ein bereits hochgeladenes Bild keinen Alt-Text, lässt sich das mit wp-agent/set-alt-text und der attachment_id nachträglich beheben (ein leerer Text ist dort bewusst erlaubt, falls du einen falschen Alt-Text gezielt wieder leeren willst).
5. Derselbe Artikel landet doppelt auf deiner Seite
Was du siehst
Nach mehreren Läufen mit denselben oder ähnlichen Eingaben existieren zwei oder mehr Beiträge mit demselben oder sehr ähnlichem Slug. (Der Slug ist der Teil der URL, der den Beitrag benennt, zum Beispiel mein-artikel in deine-seite.de/mein-artikel/.)
Warum es passiert
Ohne eine Prüfung vor dem Anlegen legt ein zweiter Lauf mit denselben Daten schlicht einen zweiten, unabhängigen Beitrag an, statt den ersten zu aktualisieren.
Was dahinter steckt
Vor jedem Anlegen sucht der Ablauf über findPostBySlug() (packages/wp-agent/src/safety/idempotency.mjs, für Artikel) beziehungsweise findPageBySlug() (packages/wp-agent/src/pipelines/landing.mjs, für Landing-Pages) nach einem bestehenden Treffer. Die Suche findet auch Entwürfe, nicht nur veröffentlichte Beiträge. Nur eine leere, erfolgreiche Antwort erlaubt eine Neuanlage. Genau ein exakter Slug-Treffer kann als vorhandenes Ziel dienen. Mehrere exakte Treffer, ausschließlich abweichende Treffer, ungültige IDs oder eine fehlerhafte Antwort sind mehrdeutig und führen ohne Auswahl eines Beitrags vor jeder Mutation zum Abbruch. Bei Artikeln ist ein eindeutiger bestehender Treffer ein Update-Plan. Der aktuelle Update-Modus von create-post-from-markdown ist jedoch manual_only, deshalb bricht die Pipeline vor einem Ersatz-Entwurf ab. Landing-Pages verwenden den getrennten Update-Pfad upsert-pattern und dessen deklarierte Update-Semantik.
Wichtig: Diese Suche läuft ausschließlich über den Slug. Ohne übergebenen slug findet keine Prüfung statt, jeder Lauf legt einen neuen Beitrag an (mit einem Slug, den WordPress automatisch aus dem Titel erzeugt).
Abhilfe
Gib bei jedem Lauf einen festen, ausdrücklichen --slug mit, nicht nur den Titel. Ändert sich der Slug zwischen zwei Läufen (zum Beispiel weil sich der Titel geändert hat und kein Slug gesetzt war), erkennt der Ablauf den alten Beitrag nicht wieder und plant einen neuen Artikel. Bestehende Artikel müssen derzeit manuell in WordPress aktualisiert werden. post_id an create-post-from-markdown oder update-post liefert HTTP 409 mit manual_only und führt kein Remote-Update aus.
Teil 2: Sonderfälle einzelner Funktionen
Diese Punkte sind keine der fünf Kernfehler, sondern konkrete Sonderfälle, die dir nur begegnen, wenn du genau diese Funktionen nutzt.
2.1 WooCommerce-Produkte: nur über den offiziellen Weg, nie über rohes Schreiben
Was du siehst
Ein Preis wurde geändert, in der Datenbank steht der neue Wert, aber die Sortierung oder Filterung nach Preis im Shop zeigt weiterhin den alten Stand.
Warum es passiert
wp-agent/update-product (packages/wp-agent-companion/includes/abilities/update-product.php) dokumentiert den tatsächlichen Fallstrick, und zwar nicht die verbreitete HPOS-Behauptung: High-Performance Order Storage betrifft nur Bestellungen, nicht Produkte. Der reale Fallstrick ist ein anderer: ein rohes Schreiben in das Preis-Feld ändert zwar den gespeicherten Wert, aber weder WooCommerces eigenen Zwischenspeicher noch die separate Nachschlage-Tabelle (wp_wc_product_meta_lookup), die der Shop für Sortierung und Filterung nach Preis nutzt. Der Preis „stimmt“ dann in der Datenbank, aber Sortierung und Filterung bleiben ohne jede Fehlermeldung auf dem alten Stand.
Wie WPAgently das löst
update-product schreibt ausschließlich über WooCommerces offizielle Setter und ruft danach save() auf. Nur save() aktualisiert Zwischenspeicher und Nachschlage-Tabelle konsistent mit. Zur Sicherheit liest wpagent das Produkt danach frisch neu ein, das ist der eigentliche Beweis, dass die Werte über den vollständigen WooCommerce-Weg gelandet sind.
Zweiter Fallstrick: doppelte SKU
Eine SKU ist die Artikelnummer eines Produkts, sie muss eindeutig sein. Ist sie bereits vergeben, würde WooCommerce ohne Vorsorge mit einem harten Fehler (HTTP 500) abstürzen. update-product prüft deshalb vorher, ob die SKU schon existiert, und liefert dann eine saubere Meldung mit der kollidierenden Produkt-ID: „Die SKU […] wird bereits von Produkt #… verwendet.“
Dritter Fallstrick: Berechtigung
WooCommerce erlaubt das Bearbeiten von Produkten standardmäßig nur Shop-Managern und Administratoren, nicht der Rolle Editor. Der übliche Editor-Bot bekommt beim Versuch deshalb ein reguläres „Keine Berechtigung“ (403), auch wenn er normale Beiträge problemlos schreibt. Das ist kein Fehler, sondern WooCommerces eigenes Berechtigungsmodell.
Abhilfe
Ausschließlich wp-agent/update-product für Produktänderungen nutzen. Bei einer SKU-Kollision die gemeldete Produkt-ID prüfen und eine eindeutige SKU wählen. Braucht der Bot Schreibrechte auf Produkten, muss ihm zusätzlich zur Editor-Rolle die WooCommerce-Berechtigung edit_products (oder die Shop-Manager-Rolle) zugewiesen werden. Das entscheidest du in WordPress selbst, außerhalb von WPAgently.
2.1a WooCommerce-Bestellungen: sichtbar, aber mit 403 gesperrt
Was du siehst
Die fünf Bestell-Abilities sind im Profil Vollzugriff oder Nur lesen sichtbar, der Agent erhält beim Aufruf aber HTTP 403. Alternativ fehlen sie vollständig, solange das Inhaltsprofil aktiv ist.
Warum es passiert
Bestellungen können Namen, Adressen, Kontaktdaten, Transaktionskennungen und vertrauliche Notizen enthalten. Deshalb gehören read_private_shop_orders und edit_shop_orders nicht zu den normalen Produktrechten und werden einem Redakteur nie automatisch gegeben. Die Bestellgruppe ist außerdem bewusst nicht Teil des empfohlenen Inhaltsprofils.
Abhilfe
Öffne WPAgently > Agent verbinden. Wähle im Abschnitt „WooCommerce-Bestellungen für den Agent-Nutzer“ den dedizierten Redakteur und aktiviere die Bestellrechte ausdrücklich. Wähle danach Vollzugriff oder Benutzerdefiniert, wenn auch Status und Notizen bearbeitet werden sollen. Nur lesen zeigt ausschließlich die drei lesenden Bestellwerkzeuge. Fordere Kunden-, Zahlungs- oder Positionsdaten nur über den jeweiligen include_*-Schalter an, wenn die konkrete Aufgabe diese Daten braucht. Statuswechsel benötigen confirm: true, Kundennotizen zusätzlich confirm_customer_notification: true. Erstattungen, Bestellanlage, Positionsänderungen und Zahlungsaktionen werden nicht unterstützt.
2.2 ACF-Felder: der field_key ist Pflicht, der Feldname reicht nicht
Was du siehst
Ein Aufruf von update-acf-field mit dem Feldnamen (zum Beispiel mein_feld statt field_65abc123) schlägt fehl, oder das Feld wird nicht wie erwartet aktualisiert. (ACF, Advanced Custom Fields, ist ein sehr verbreitetes Plugin für eigene Zusatzfelder an Beiträgen und Seiten.)
Warum es passiert
ACF akzeptiert technisch auch den Feldnamen, versucht ihn dann aber selbst aufzulösen. Das ist ACFs eigener Notbehelf, nicht die empfohlene Adressierung: sie wird mehrdeutig, sobald zwei Feldgruppen ein Feld mit demselben Namen haben, und funktioniert grundsätzlich nicht für Unterfelder von Repeater-, Flexible-Content- oder Group-Feldern. Deshalb verlangt WPAgently den eindeutigen internen Schlüssel (field_key), der immer mit field_ beginnt.
Wie WPAgently das löst
wp-agent/update-acf-field (packages/wp-agent-companion/includes/abilities/update-acf-field.php) akzeptiert nur den echten field_-Key eines obersten, dem Zielobjekt aktuell zugeordneten Feldes. Als Ziel sind Beiträge, Benutzer, Begriffe, Kommentare und ACF-Optionsspeicher möglich. Vor dem Schreiben prüft WPAgently die objektspezifische Bearbeitungsberechtigung und den Wert rekursiv gegen die Felddefinition, verlangt einen aktuellen Zustands-Hash und setzt eine Kurzzeitsperre. Danach wird der unformatierte Wert erneut gelesen. Bei einer Abweichung stellt der Pfad den vorherigen Zustand wieder her und bestätigt auch den Rollback.
Wie du an den field_key kommst
wp-agent/get-acf-fields liefert alle dem Zielobjekt zugeordneten ACF-Felder inklusive field_-Key, Name, Label, Typ, portabler Rohform, kanonischem acf_object_id, state_hash, Schreibbarkeit und Anzahl redigierter Referenzen. Beiträge, Medien oder Benutzer ohne Leserecht werden nicht offengelegt. Nicht sicher abbildbare Feldtypen, unbekannte Flexible-Content-Layouts, übergroße Altwerte und Felder ohne erforderliches Zusatzrecht erscheinen lesbar, aber nicht schreibbar.
Abhilfe
Vor jedem update-acf-field-Aufruf zuerst get-acf-fields für dasselbe Ziel aufrufen. Für Beiträge bleibt { "post_id": 42 } kompatibel. Für andere Ziele müssen object_type und object_id gemeinsam übergeben werden, zum Beispiel { "object_type": "user", "object_id": 7 }. Für den generischen Optionsspeicher lautet das Ziel { "object_type": "options", "object_id": "options" }. Aus derselben Feldantwort müssen key und state_hash unverändert als field_key und expected_hash übernommen werden. Bei HTTP 409 den Feldstand neu lesen, nicht denselben Schreibversuch blind wiederholen. Datumsfelder verwenden die unformatierte ACF-Speicherform, bei date_picker also YYYYMMDD. Optionsseiten-Definitionen selbst gehören nicht zu diesem Wertepfad und sind ohne passende ACF-Laufzeit nicht verfügbar.
2.2a ACF-Datenmodell: Definition nicht portabel oder Laufzeit-Slug belegt
Symptom
update-acf-post-type oder update-acf-taxonomy antwortet mit HTTP 409. In der vorherigen Leseantwort steht portable: false oder registration_conflict: true. Beim Löschen kann stattdessen eine zusätzliche Bestätigung für vorhandene Inhalte oder Begriffe fehlen.
Ursache
Der begrenzte Schreibpfad bildet nur die in der Ability-Referenz genannten Eigenschaften verlustfrei ab. Zusätzliche ACF-Einstellungen werden nicht still entfernt. registration_conflict: true bedeutet, dass ein Theme, Plugin oder eine PHP-Definition denselben Inhaltstyp- oder Taxonomie-Slug bereits in WordPress registriert. Ein Löschen der ACF-Definition entfernt absichtlich keine Beiträge oder Begriffe.
Lösung
Lies die Definition unmittelbar vor einem Write mit get-acf-post-type oder get-acf-taxonomy erneut und übernimm den aktuellen hash als expected_hash. Ändere nicht portable oder kollidierende Definitionen in ACF beziehungsweise in der Komponente, der die Registrierung gehört. Setze confirm_content=true nur, wenn ausdrücklich gewollt ist, dass die Definition verschwindet, während ihre Inhalte oder Begriffe verborgen in der Datenbank bleiben.
2.3 Contact Form 7: hier hat das normale Inhaltsfeld keine Wirkung
Was du siehst
Ein Schreibversuch auf ein Contact-Form-7-Formular „gelingt“ scheinbar, ohne dass sich am tatsächlichen Formular etwas ändert.
Warum es passiert
Contact Form 7 speichert die eigentliche Formularkonfiguration in einem eigenen Feld (_form), nicht im normalen Inhaltsfeld (post_content). Ein Write auf post_content verifiziert sich zwar sauber (der Wert steht ja in der Datenbank), hat aber keine sichtbare Wirkung, weil Contact Form 7 dieses Feld beim Anzeigen des Formulars gar nicht liest.
Zwei sichere Pfade
Erste Ebene: create-post-from-markdown lässt nur öffentliche Post-Typen zu. Der von Contact Form 7 genutzte Typ ist nicht öffentlich, ein Versuch wird deshalb mit „Post-Type wpcf7_contact_form ist nicht öffentlich und über diese Ability nicht zugänglich“ abgelehnt.
Zweiter Pfad: Nutze für Formulare ausschließlich wp-agent/list-contact-forms, get-contact-form, create-contact-form, update-contact-form, duplicate-contact-form und delete-contact-form. Diese Abilities arbeiten über Contact Form 7s eigene Objekt-, Sanitizer- und Validierungs-APIs und lesen das Ergebnis nach dem Speichern erneut ein. render-verify warnt weiterhin mit cf7_post_content_inert, falls ein generischer Inhalts-Write am richtigen Formularspeicher vorbeiginge.
Abhilfe
Verwende die sechs nativen Formular-Abilities oder die normale Contact-Form-7-Oberfläche. Schreibe niemals über update-post oder roh in _form. Dauerhaftes Löschen verlangt zusätzlich confirm: true. Willst du Formulare nur lesen lassen, deaktiviere die vier schreibenden Formular-Abilities im benutzerdefinierten WPAgently-Profil.
2.4 Gravity Forms: Redakteur hat keine Formularrechte
Was du siehst
Die Gravity-Forms-Abilities sind registriert, werden für den Agent-Nutzer aber als nicht erlaubt gemeldet.
Warum es passiert
Gravity Forms verwendet für Formulare die eigenen Capabilities gravityforms_edit_forms, gravityforms_create_form und gravityforms_delete_forms. Einträge nutzen getrennt gravityforms_view_entries, gravityforms_edit_entries und gravityforms_delete_entries. Ein normaler WordPress-Redakteur erhält diese Rechte nicht automatisch. Den Agent-Nutzer zum Administrator zu machen wäre dafür unnötig und würde die Sicherheitsgrenze der Verbindung aufheben.
Abhilfe
Öffne WPAgently > Agent verbinden. Im Abschnitt „Gravity Forms für den Agent-Nutzer“ wählst du den dedizierten Redakteur und klickst für Formulare auf „Gravity-Forms-Rechte aktivieren“. Eintragsrechte aktivierst du darunter separat. Dieser zweite Schalter warnt ausdrücklich vor Personen- und Zahlungsdaten und wird beim Anlegen eines Agent-Nutzers nie automatisch betätigt. WPAgently protokolliert nur die dabei tatsächlich ergänzten Capabilities. Beim Entfernen oder bei der Plugin-Deinstallation werden ausschließlich diese Ergänzungen entfernt. Bereits vorhandene Rollenrechte bleiben bestehen.
Für Formulare verwendest du danach ausschließlich die sechs gravity-form-Werkzeuge. Einträge liest und bearbeitest du nur mit den fünf gravity-entry-Werkzeugen. list-gravity-entries gibt sensible Detailwerte nur mit include_values: true aus. Dauerhafte Löschpfade verlangen confirm: true. Zahlungsänderungen, neue Einreichungen, Notizen, Transaktionen und unbekannte Add-on-Metadaten gehören nicht zu dieser Integration.
2.5 WPForms: native Abilities sichtbar, aber als Redakteur gesperrt
Was du siehst
Native WPForms-Abilities sind registriert, der sichere Agent-Nutzer erhält beim Aufruf aber HTTP 403. Schreibwerkzeuge können außerdem vollständig fehlen. WPForms Lite zeigt keine Eintrags-Abilities.
Warum es passiert
WPForms beschränkt seine nativen Abilities standardmäßig auf Administratoren. WPAgently macht den Agent-Nutzer deshalb nicht zum Administrator. Stattdessen gibt es drei getrennte Rechte für Formular-Lesezugriff, sensible Eintragsdaten und Formularänderungen. Diese Ergänzungen gelten ausschließlich am WPAgently-MCP-Endpunkt. Sie öffnen weder die normale WPForms-Verwaltung noch fremde REST- oder AJAX-Pfade. Der eigene WPForms-Schreibschalter bleibt ein zusätzliches, unabhängiges Gate. Von WPForms nicht für MCP veröffentlichte Abilities bleiben geschlossen.
Abhilfe
Öffne WPAgently > Agent verbinden und aktiviere für den dedizierten Redakteur nur die benötigte WPForms-Rechtegruppe. Eintragsrechte werden nie automatisch vergeben. IP-Adressen in nativen Eintragsantworten werden für diesen Nutzer maskiert. Für Schreibzugriff aktivierst du zusätzlich unter WPForms > Werkzeuge > AI MCP den WPForms-Schreibschalter und danach im WPAgently-Kontrollzentrum die native Schreibgruppe. Wird WPForms später deaktiviert, bleiben von WPAgently protokollierte Rechte auf der Verbindungsseite sichtbar und widerrufbar. Bei der Deinstallation entfernt WPAgently ausschließlich selbst ergänzte Rechte.
2.6 Ninja Forms: breite Provider-Rechte sicher begrenzen
Was du siehst
Native Ninja-Forms-Werkzeuge sind sichtbar, der Redakteur erhält aber HTTP 403. Einige Lösch-, Einstellungs- und Einreichungswerkzeuge erscheinen auch nach einer Freigabe nicht.
Warum es passiert
Ninja Forms schützt seine nativen Abilities mit dem breiten Recht nf_edit_forms. Dieses Recht dauerhaft an einen Redakteur zu vergeben würde gleichzeitig harmlose Lesewege, personenbezogene Einreichungen, Formularänderungen und E-Mail-Auslösung öffnen. WPAgently ersetzt diese breite Dauerfreigabe deshalb durch vier eigene, widerrufbare Rechte. Acht riskante Provider-Pfade bleiben bewusst in Quarantäne. Dazu gehören Formular-, Feld-, Aktions-, Berechnungs- und Einreichungslöschungen, globale Plugin-Einstellungen und ungeprüfte Einreichungsänderungen.
Abhilfe
Öffne WPAgently > Agent verbinden und aktiviere nur die benötigte Gruppe. Das Formularlesen wird für einen neu angelegten Agent-Nutzer vorbereitet. Einreichungen, Writes und E-Mail-Auslösung bleiben aus, bis du sie einzeln freigibst. process-submission kann echte E-Mails versenden und benötigt deshalb zusätzlich die eigene Delivery-Gruppe. Das Recht nf_edit_forms wird nur während eines passenden, vollständig geprüften Aufrufs am WPAgently-MCP-Endpunkt vermittelt. Es wird nie am Nutzerkonto gespeichert. Gemischte JSON-RPC-Batches und Batches mit mehr als 100 Nachrichten werden abgelehnt.
2.7 Page-Builder-Datensatz vorhanden, aber Plugin nicht aktiv
Was du siehst
render-check meldet einen gültigen Zustand, aber die Seite sieht im Frontend anders aus als erwartet, oder wie eine leere/rohe Version. (Ein Page-Builder wie Elementor, Bricks, Breakdance oder Oxygen baut Seiten in einer eigenen Oberfläche und speichert sie in einem eigenen Format, nicht als normale WordPress-Blöcke.)
Warum es passiert
Diese Builder speichern ihren Inhalt in eigenen versteckten Feldern, nicht im normalen Inhaltsfeld. Ist so ein Datensatz vorhanden, das zugehörige Plugin aber nicht aktiv (deaktiviert, gelöscht oder nie installiert), zeigt das Frontend nur das rohe Inhaltsfeld, nie die Builder-Ausgabe. Ob ein Plugin aktiv ist, sagt allein noch nichts darüber, wie ein konkreter Beitrag gespeichert ist.
Was dahinter steckt
wp-agent/detect-builder (packages/wp-agent-companion/includes/abilities/detect-builder.php) erkennt pro Beitrag am tatsächlichen Speicherformat, womit der Inhalt gespeichert ist. wp-agent/render-verify prüft zusätzlich, ob der erkannte Builder tatsächlich geladen ist. Der Check heißt builder_render_mismatch; agrees:false mit severity:warn bedeutet: Builder-Datensatz vorhanden, Plugin nicht aktiv, das Frontend zeigt nur rohen Inhalt.
Abhilfe
Vor einer Bearbeitung wp-agent/detect-builder auf den Ziel-Beitrag aufrufen und die Felder primary und storage_location prüfen. Zeigt storage_location „postmeta“ oder „gemischt“, liegen Builder-Daten außerhalb des normalen Inhaltsfelds; Änderungen über create-post-from-markdown/update-post wirken sich dann nicht sichtbar aus, solange das Builder-Plugin nicht aktiv ist. Nach jeder Änderung wp-agent/render-verify laufen lassen und den builder_render_mismatch-Check auf agrees:true prüfen, bevor der Beitrag als erledigt gilt.
2.8 SEO-Read-back-Falle bei Yoast (seit Version 14)
Was du siehst
Nach einer manuellen Änderung im Yoast-Backend erscheint im Google-Suchergebnis oder in der Yoast-Vorschau weiterhin der alte Titel oder die alte Beschreibung.
Warum es passiert
Rank Math liest Titel und Beschreibung beim Anzeigen aus denselben Feldern, in die auch geschrieben wird. Yoast dagegen zeigt seit Version 14 aus einer eigenen Zusatz-Tabelle (wp_yoast_indexable) an, nicht direkt aus den Meta-Feldern. Nach einer manuellen Änderung kann das Frontend deshalb noch alte Werte aus der Zusatz-Tabelle zeigen, bis Yoast den Indexable neu aufbaut.
Was dahinter steckt
Der aktuelle set-seo-meta-Writer schreibt überhaupt nicht. Die Falle liegt ausschließlich in der nachgelagerten Anzeige einer manuellen Änderung, deshalb prüft wp-agent/render-verify sie separat im Check seo_read_back. Bei aktivem Yoast liest es die wirksame Presentation über YoastSEO()->meta->for_post(). Diese Meta Surface berücksichtigt in Produktion das persistierte Indexable und in lokalen Umgebungen Yoasts bewusst nur im Speicher aufgebaute Presentation. Weicht die wirksame Ausgabe von den gespeicherten und über Yoast aufgelösten Werten ab, meldet der Check severity:warn. Rank Math und SEOPress werden über ihre provider-eigenen Metafelder geprüft, AIOSEO über seine native SEO-Ability.
Der Yoast-Zweig läuft in der praktischen MySQL-Kompatibilitätsmatrix gegen eine echte aktive Installation. AIOSEO benötigt für seinen eigenen Datenbank-Layer MariaDB. Die echte AIOSEO-Laufzeitabdeckung bleibt auf MariaDB beschränkt. Das Plugin ist mit der offiziellen SQLite-Integration nicht zuverlässig aktivierbar, deshalb wird AIOSEO nicht auf SQLite als kompatibel ausgewiesen.
Abhilfe
Nach einer manuellen Yoast-Änderung zusätzlich wp-agent/get-seo-meta und wp-agent/render-verify aufrufen und den seo_read_back-Check prüfen. Bleibt agrees:false, muss die Yoast-Neuindexierung unter Yoast SEO > Werkzeuge ausgeführt und der Beitrag danach erneut geprüft werden.
2.9 Builder-Caches nach dem Bearbeiten (getrennt von Punkt 3)
Was du siehst
Nach einer Bearbeitung eines Beitrags mit Spectra-, GenerateBlocks- oder WooCommerce-Produkt-Inhalten zeigt das Frontend noch alte Darstellungen oder veraltete Produkt-Daten (Preisfilter, verwandte Produkte), obwohl render-check den neuen Inhalt korrekt bestätigt.
Warum es passiert
Einige Builder pflegen einen eigenen Zwischenspeicher, der bei einem programmatischen Write nicht automatisch geleert wird, unabhängig vom Sitemap-Problem aus Punkt 3.
Was dahinter steckt
wp-agent/refresh-builder-cache (packages/wp-agent-companion/includes/abilities/refresh-builder-cache.php) löst pro erkanntem Builder die jeweils passende offizielle Leerung aus. Empirisch gegen die installierten Plugin-Versionen geprüft:
- Spectra 2.20.1: Asset-Version aktualisieren plus gezieltes Löschen der Seiten-Assets (wie der offizielle Befehl
wp spectra regenerate-css). - GenerateBlocks 2.3.0: dieselbe Methode, die das Plugin selbst beim Speichern aufruft.
- WooCommerce 10.9.4:
wc_delete_product_transients(), löscht globale und produktspezifische Zwischenspeicher. - Kadence Blocks 3.7.8 und WPBakery: selbstheilend beim Anzeigen, keine Aktion nötig.
- Elementor 4.2.1: praktisch geprüft. Der native Dokumentpfad nutzt
Document::save(), löscht dadurch Post-CSS und Dokumentcache und bestätigt das Ergebnis über vollständigen Read-back plus Frontend-Render. Responsive Element-Styles werden zusätzlich aus der öffentlichen Control- und Breakpoint-Registry abgeleitet, streng nach Control-Typ validiert und über Elementors öffentliche Post-CSS-Klasse kompiliert und geprüft. Der allgemeine Cache-Wrapper nutzt zusätzlich die geladene offiziellefiles_manager->clear_cache()-API. - Breakdance und Oxygen: nutzt eine geladene In-Request-Schnittstelle, falls vorhanden. Ohne lizenzierte Testdatei bleibt der Pfad defensiv und nicht praktisch zertifiziert.
- Bricks: im zugrunde liegenden Bauplan nicht in der Cache-Liste, deshalb keine Aktion [zu klären: ob Bricks einen eigenen Cache-Mechanismus braucht, ist im Code offen gelassen].
Abhilfe
Nach Bearbeitungen an Beiträgen mit Spectra-, GenerateBlocks- oder WooCommerce-Produkt-Inhalten zusätzlich zu refresh-hooks (Punkt 3) auch wp-agent/refresh-builder-cache mit der post_id aufrufen. Anders als refresh-hooks ist dieser Schritt aktuell NICHT automatisch Teil der Artikel- oder Landing-Abläufe [zu klären: ob refresh-builder-cache künftig automatisch in die Abläufe aufgenommen wird]. Prüfe im Ergebnis pro Aktion das Feld verified; steht dort false bei executed:true, konnte die Leerung zwar ausgelöst, aber nicht bestätigt werden.
Teil 3: Werkzeuge zur Selbstdiagnose
Diese Werkzeuge verändern nichts, sie zeigen nur an (alle sind readonly:true). Sie sind der richtige erste Schritt, bevor du eine Support-Anfrage stellst. Du rufst sie über deinen Assistenten (Claude Code/Codex) auf; der Toolname ist der Ability-Name mit Bindestrich statt Schrägstrich, zum Beispiel wp-agent-render-verify.
| Werkzeug | Zeigt |
|---|---|
wp-agent/render-check | Blockanzahl, core/freeform-Anzahl, gerendertes HTML, Wortzahl für einen Beitrag |
wp-agent/render-verify | Die tatsächliche Frontend-Ausgabe plus vier Fallen-Checks: Freeform, Builder-Mismatch, CF7-Falle, SEO-Read-Back |
wp-agent/detect-builder | Welcher Page-Builder/Welches Format ein Beitrag tatsächlich nutzt, mit allen gefundenen Signalen und deren Speicherort |
wp-agent/get-acf-fields | Alle einem berechtigten Beitrag, Benutzer, Begriff, Kommentar oder Optionsspeicher zugeordneten ACF-Felder mit field_-Key, portabler Rohform, Objekt-ID, Zustands-Hash, Schreibbarkeit und Referenzredaktion |
wp-agent/list-media | Vorhandene Mediendateien [zu klären: genaue Filter-/Sortieroptionen nicht aus dem gelesenen Code belegt] |
wp-agent/get-post | Aktueller Stand eines Beitrags einschließlich Rohinhalt [zu klären: genauer Funktionsumfang nicht Teil dieser Analyse] |
wp-agent doctor, wp-agent preflight und wp-agent abilities (CLI-Befehle, kein Assistenten-Tool) prüfen dagegen die Verbindung selbst, nicht einzelne Inhalte; siehe docs/setup.md, Abschnitt „Diagnose“.
Anhang: Fehlermeldungen zum Wiedererkennen
Zum schnellen Wiedererkennen in Protokollen oder Assistenten-Ausgaben. Die Formulierungen stammen aus dem Code; N, <plugin> und Auslassungspunkte stehen hier für variable Teile.
| Fehlermeldung | Quelle | Bedeutet |
|---|---|---|
| „Abgebrochen: Bei der Umwandlung sind N Freiform-Blöcke entstanden. So wird verhindert, dass unbemerkt der alte Classic-Editor verwendet wird.“ | article.mjs | Fehler 1 |
| „Abgebrochen: Beim Aufbau der Landingpage sind N Freiform-Blöcke entstanden. So wird verhindert, dass unbemerkt der alte Classic-Editor verwendet wird.“ | landing.mjs | Fehler 1 |
| „Abgebrochen: Die Darstellungsprüfung meldet N Freiform-Blöcke.“ | article.mjs, landing.mjs | Fehler 1, zweite Prüfung |
| „Abgebrochen: Die SEO-Angaben wurden nicht gespeichert (Plugin <plugin>).“ | article.mjs | Fehler 2 |
| „Abgebrochen: Das Beitragsbild oder der Alt-Text konnte nicht bestätigt werden.“ | article.mjs | Fehler 4 |
| „Für das Beitragsbild ist ein Alt-Text Pflicht.“ | media.mjs | Fehler 4, fehlender Alt-Text vor dem Upload |
| „SSRF-Schutz: Erlaubt sind nur http- und https-Adressen …“ | ssrf.mjs | Fehler 4, verbotenes URL-Schema vor CLI-Download |
| „SSRF-Schutz: Diese Adresse zeigt auf das eigene System (localhost) …“ | ssrf.mjs | Fehler 4, lokaler Host vor CLI-Download |
| „SSRF-Schutz: Diese Adresse führt in ein privates oder lokales Netz …“ | ssrf.mjs | Fehler 4, private Adresse vor CLI-Download |
| „SSRF-Schutz: Der Host … lässt sich nicht auflösen …“ | ssrf.mjs | Fehler 4, DNS-Fehlschlag vor CLI-Download |
| „SSRF-Schutz: nur http/https erlaubt …“ | upload-media.php | Fehler 4, verbotenes URL-Schema im direkten WordPress-Upload |
| „SSRF-Schutz: localhost/Loopback ist blockiert …“ | upload-media.php | Fehler 4, interner Hostname |
| „SSRF-Schutz: Ziel-IP … liegt in einem privaten/Loopback/Link-Local-Bereich …“ | upload-media.php | Fehler 4, private IP nach DNS-Auflösung |
| „SSRF-Schutz: Host … nicht auflösbar, Download verweigert.“ | upload-media.php | Fehler 4, DNS-Fehlschlag, fail-closed |
„Die SKU <SKU> wird bereits von Produkt #… verwendet.“ | update-product.php | 2.1, WooCommerce-SKU-Kollision |
„field_key muss der ACF-Field-Key sein (beginnt mit field_), nicht der Feldname.“ | update-acf-field.php | 2.2, ACF-Feldname statt Key |
„Post-Type wpcf7_contact_form ist nicht öffentlich und über diese Ability nicht zugänglich.“ | helpers.php (wpagent_validate_post_type) | 2.3, CF7 über create-post-from-markdown blockiert |
„post_content bei CF7 rendert nicht als Formular; die Konfiguration liegt in postmeta _form.“ | render-verify.php | 2.3, CF7-Falle erkannt |