`discourse_permalink` vacío causa fallos silenciosos en la sincronización de comentarios

Entorno

  • Versión de WP-Discourse: 2.5.7

  • Versión de Discourse: 3.5.0.beta5-dev

  • Versión de WordPress: 6.9

  • Alojamiento: WP Engine (WordPress multisite)

  • Instancia de Discourse: Autoalojada / Alojamiento de Discourse (forum.avweb.com)

Resumen

Las publicaciones publicadas desde WordPress crean temas con éxito en Discourse, pero discourse_permalink se almacena como una cadena vacía en los metadatos de la publicación. Esto provoca que todas las sincronizaciones de comentarios posteriores fallen silenciosamente, ya que sync_comments() en discourse-comment.php se detiene prematuramente cuando el permalink está vacío. El ID del tema de Discourse y la bandera de sincronización de webhook se escriben correctamente; solo falta el permalink.

Identificamos 174 publicaciones afectadas en un solo sitio de nuestra red multisitio. El problema parece haber comenzado alrededor de mediados de diciembre de 2025.

Pasos para Reproducir

  1. Publicar una entrada de WordPress configurada para crear un tema de Discourse

  2. El tema se crea con éxito en Discourse

  3. discourse_topic_id se guarda en los metadatos de la publicación ✓

  4. wpdc_sync_post_comments se establece en 1 (el webhook se activa correctamente) ✓

  5. discourse_permalink se guarda como una cadena vacía ✗

  6. discourse_comments_raw nunca se escribe (la sincronización nunca se completa)

Comportamiento Esperado

discourse_permalink debería contener la URL completa del tema de Discourse (p. ej., The China Chickens Come Home - AVweb - News Discussion - AVweb.com Discussion) después de la creación exitosa del tema.

Comportamiento Actual

discourse_permalink existe como clave de metadatos pero su valor está vacío. Cada llamada posterior a sync_comments() alcanza esta comprobación en la línea 209 de lib/discourse-comment.php y retorna anticipadamente:

if ( ! $discourse_permalink ) {
    return 0;
}

Debido a que la sincronización nunca se completa, discourse_last_sync nunca se escribe, por lo que el complemento reintenta en cada carga de página, y falla cada vez.

Diagnóstico

Rastreé el problema a través de la siguiente ruta de código:

  1. DiscourseCommentFormatter::format() llama a do_action('wpdc_sync_discourse_comments') lo que activa DiscourseComment::sync_comments()

  2. sync_comments() comprueba discourse_permalink — lo encuentra vacío, devuelve 0

  3. format() luego comprueba discourse_comments_raw en los metadatos personalizados de la publicación — lo encuentra ausente, devuelve bad_response_html()

  4. Los comentarios nunca se muestran para la publicación afectada

El flujo de creación de temas está escribiendo discourse_topic_id pero no logra persistir el permalink. Pudimos reconstruir los permalinks correctos consultando la API de Discourse en /t/{topic_id}.json y escribiéndolos de nuevo en los metadatos de la publicación, lo que resolvió la sincronización para las 174 publicaciones.

Solución Temporal

Escribimos un script de reparación de WP-CLI que:

  1. Encuentra publicaciones donde wpdc_sync_post_comments = 1 pero discourse_comments_raw está ausente

  2. Obtiene la etiqueta del tema de /t/{topic_id}.json

  3. Reconstruye y guarda el permalink

  4. Obtiene y guarda los comentarios de /t/{slug}/{topic_id}/wordpress.json

Estamos ejecutando esto en un cron programado como medida provisional.


Problemas Adicionales Encontrados Durante la Investigación

1. Bloqueo Global de MySQL Causa Fallos Silenciosos de Sincronización

Archivo: lib/discourse-comment.php, línea 176

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

sync_comments() utiliza un único bloqueo global de MySQL (discourse_lock) compartido entre todas las publicaciones de toda la instalación. El tiempo de espera es 0 (no bloqueante), por lo que si alguna publicación se está sincronizando actualmente, todas las demás publicaciones omiten silenciosamente su sincronización; sin registro, sin reintento.

En un sitio de alto volumen que publica varias entradas por día, esto crea una condición de carrera en la que las publicaciones pierden consistentemente el bloqueo y nunca se sincronizan. Combinado con el período de sincronización de 10 minutos, una publicación puede quedarse permanentemente atascada si pierde el bloqueo en sus primeros intentos y luego el problema del permalink vacío impide futuras sincronizaciones.

Corrección sugerida: Usar un bloqueo por publicación:

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

Con la liberación correspondiente:

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

2. Doble Escape de discourse_comments_raw

Archivo: lib/discourse-comment.php, línea 222

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

update_post_meta() utiliza $wpdb->prepare() internamente, por lo que envolver el valor en esc_sql() causa un doble escape. En múltiples ciclos de sincronización, las comillas en el cuerpo JSON acumulan caracteres de escape (\\\"\\\\\\\"\\\\\\\\\\\\\\\") hasta que el JSON se vuelve irrecuperable.

Corrección sugerida:

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

Impacto

Estos problemas se acumulan: el permalink vacío evita la sincronización inicial, el bloqueo global evita la recuperación y el doble escape puede corromper los datos de las publicaciones que logran sincronizarse. En nuestra instalación, 174 publicaciones (aproximadamente 2 meses de contenido) se vieron afectadas antes de que identificáramos la causa raíz.