Leeres `discourse_permalink` verursacht stille Kommentar-Synchronisationsfehler

Umgebung

  • WP-Discourse Version: 2.5.7

  • Discourse Version: 3.5.0.beta5-dev

  • WordPress Version: 6.9

  • Hosting: WP Engine (WordPress Multisite)

  • Discourse-Instanz: Selbst gehostet / Discourse Hosting (forum.avweb.com)

Zusammenfassung

Beiträge, die aus WordPress veröffentlicht werden, erstellen erfolgreich Themen in Discourse, aber discourse_permalink wird als leerer String in den Beitrags-Metadaten gespeichert. Dies führt dazu, dass alle nachfolgenden Kommentar-Synchronisierungen stillschweigend fehlschlagen, da sync_comments() in discourse-comment.php frühzeitig abbricht, wenn der Permalink leer ist. Die Discourse-Themen-ID und das Webhook-Sync-Flag werden korrekt geschrieben – nur der Permalink fehlt.

Wir haben 174 betroffene Beiträge auf einer einzigen Website in unserem Multisite-Netzwerk identifiziert. Das Problem scheint Mitte Dezember 2025 begonnen zu haben.

Schritte zur Reproduktion

  1. Veröffentlichen Sie einen WordPress-Beitrag, der so konfiguriert ist, dass ein Discourse-Thema erstellt wird

  2. Thema wird erfolgreich in Discourse erstellt

  3. discourse_topic_id wird in Beitrags-Metadaten gespeichert ✓

  4. wpdc_sync_post_comments wird auf 1 gesetzt (Webhook feuert korrekt) ✓

  5. discourse_permalink wird als leerer String gespeichert ✗

  6. discourse_comments_raw wird nie geschrieben (Sync wird nie abgeschlossen)

Erwartetes Verhalten

discourse_permalink sollte nach erfolgreicher Themen-Erstellung die vollständige Discourse-Themen-URL enthalten (z. B. The China Chickens Come Home - AVweb - News Discussion - AVweb.com Discussion).

Tatsächliches Verhalten

discourse_permalink existiert als Metaschlüssel, aber sein Wert ist leer. Jeder nachfolgende Aufruf von sync_comments() trifft auf diese Absicherung in Zeile 209 von lib/discourse-comment.php und kehrt frühzeitig zurück:

if ( ! $discourse_permalink ) {
    return 0;
}

Da der Sync nie abgeschlossen wird, wird discourse_last_sync nie geschrieben, sodass das Plugin bei jedem Seitenaufruf erneut versucht – und jedes Mal fehlschlägt.

Diagnose

Wir haben das Problem anhand des folgenden Code-Pfades nachverfolgt:

  1. DiscourseCommentFormatter::format() ruft do_action('wpdc_sync_discourse_comments') auf, was DiscourseComment::sync_comments() auslöst

  2. sync_comments() prüft auf discourse_permalink – findet ihn leer, gibt 0 zurück

  3. format() prüft dann auf discourse_comments_raw in den Beitrags-Metadaten – findet ihn fehlend, gibt bad_response_html() zurück

  4. Kommentare werden für den betroffenen Beitrag nie angezeigt

Der Themen-Erstellungs-Flow schreibt discourse_topic_id, versäumt es aber, den Permalink zu speichern. Wir konnten die korrekten Permalinks rekonstruieren, indem wir die Discourse API unter /t/{topic_id}.json abfragten und sie zurück in die Beitrags-Metadaten schrieben, was die Synchronisierung für alle 174 Beiträge behob.

Workaround

Wir haben ein WP-CLI-Reparatur-Skript geschrieben, das:

  1. Beiträge findet, bei denen wpdc_sync_post_comments = 1 ist, aber discourse_comments_raw fehlt

  2. Den Themen-Slug von /t/{topic_id}.json abruft

  3. Den Permalink rekonstruiert und speichert

  4. Kommentare von /t/{slug}/{topic_id}/wordpress.json abruft und speichert

Wir führen dies als Übergangslösung über einen geplanten Cronjob aus.


Zusätzliche Probleme bei der Untersuchung gefunden

1. Globaler MySQL-Lock verursacht stille Sync-Fehler

Datei: lib/discourse-comment.php, Zeile 176

$got_lock = $wpdb->get_row( "SELECT GET_LOCK( 'discourse_lock', 0 ) got_it" );

sync_comments() verwendet einen einzigen globalen MySQL-Lock (discourse_lock), der für alle Beiträge in der gesamten Installation gemeinsam genutzt wird. Das Timeout ist 0 (nicht blockierend), sodass, wenn ein Beitrag gerade synchronisiert wird, alle anderen Beiträge ihren Sync stillschweigend überspringen – keine Protokollierung, kein erneuter Versuch.

Auf einer Website mit hohem Volumen, auf der täglich mehrere Beiträge veröffentlicht werden, entsteht eine Race Condition, bei der Beiträge den Lock konsistent verlieren und nie synchronisiert werden. In Kombination mit dem 10-minütigen Sync-Zeitraum kann ein Beitrag dauerhaft stecken bleiben, wenn er bei seinen ersten Versuchen den Lock verliert und dann das Problem mit dem leeren Permalink zukünftige Syncs verhindert.

Vorgeschlagene Korrektur: Verwendung eines Pro-Beitrags-Locks:

$got_lock = $wpdb->get_row( "SELECT GET_LOCK( 'discourse_lock_{$post_id}', 0 ) got_it" );

Mit der entsprechenden Freigabe:

$wpdb->get_results( "SELECT RELEASE_LOCK( 'discourse_lock_{$post_id}' )" );

2. Doppelte Maskierung von discourse_comments_raw

Datei: lib/discourse-comment.php, Zeile 222

update_post_meta( $post_id, 'discourse_comments_raw', esc_sql( $raw_body ) );

update_post_meta() verwendet intern $wpdb->prepare(), sodass das Umschließen des Wertes mit esc_sql() zu einer doppelten Maskierung führt. Über mehrere Sync-Zyklen sammeln sich Anführungszeichen im JSON-Body an (\\"\\\\\\"\\\\\\\\\\\\\\"), bis das JSON nicht mehr analysierbar ist.

Vorgeschlagene Korrektur:

update_post_meta( $post_id, 'discourse_comments_raw', $raw_body );

Auswirkungen

Diese Probleme summieren sich: Der leere Permalink verhindert den anfänglichen Sync, der globale Lock verhindert die Wiederherstellung, und die doppelte Maskierung kann Daten für Beiträge beschädigen, die es schaffen, synchronisiert zu werden. Auf unserer Installation waren 174 Beiträge (etwa 2 Monate Inhalt) betroffen, bevor wir die eigentliche Ursache identifizierten.