Empty `discourse_permalink` Causes Silent Comment Sync Failures

Environment

  • WP-Discourse version: 2.5.7

  • Discourse version: 3.5.0.beta5-dev

  • WordPress version: 6.9

  • Hosting: WP Engine (WordPress multisite)

  • Discourse instance: Self-hosted / Discourse hosting (forum.avweb.com)

Summary

Posts published from WordPress successfully create topics on Discourse, but discourse_permalink is stored as an empty string in post meta. This causes all subsequent comment syncs to silently fail, as sync_comments() in discourse-comment.php bails early when the permalink is empty. The Discourse topic ID and webhook sync flag are written correctly — only the permalink is missing.

We identified 174 affected posts on a single site in our multisite network. The issue appears to have started around mid-December 2025.

Steps to Reproduce

  1. Publish a WordPress post configured to create a Discourse topic

  2. Topic is created successfully on Discourse

  3. discourse_topic_id is saved to post meta ✓

  4. wpdc_sync_post_comments is set to 1 (webhook fires correctly) ✓

  5. discourse_permalink is saved as an empty string ✗

  6. discourse_comments_raw is never written (sync never completes)

Expected Behavior

discourse_permalink should contain the full Discourse topic URL (e.g., The China Chickens Come Home - AVweb - News Discussion - AVweb.com Discussion ) after successful topic creation.

Actual Behavior

discourse_permalink exists as a meta key but its value is empty. Every subsequent call to sync_comments() hits this guard on line 209 of lib/discourse-comment.php and returns early:

if ( ! $discourse_permalink ) {
    return 0;
}

Because the sync never completes, discourse_last_sync is never written, so the plugin retries on every page load — and fails every time.

Diagnosis

We traced the issue through the following code path:

  1. DiscourseCommentFormatter::format() calls do_action('wpdc_sync_discourse_comments') which triggers DiscourseComment::sync_comments()

  2. sync_comments() checks for discourse_permalink — finds it empty, returns 0

  3. format() then checks for discourse_comments_raw in post custom — finds it missing, returns bad_response_html()

  4. Comments never display for the affected post

The topic creation flow is writing discourse_topic_id but failing to persist the permalink. We were able to reconstruct the correct permalinks by querying the Discourse API at /t/{topic_id}.json and writing them back to post meta, which resolved the sync for all 174 posts.

Workaround

We wrote a WP-CLI repair script that:

  1. Finds posts where wpdc_sync_post_comments = 1 but discourse_comments_raw is missing

  2. Fetches the topic slug from /t/{topic_id}.json

  3. Reconstructs and saves the permalink

  4. Fetches and saves comments from /t/{slug}/{topic_id}/wordpress.json

We’re running this on a scheduled cron as a stopgap.


Additional Issues Found During Investigation

1. Global MySQL Lock Causes Silent Sync Failures

File: lib/discourse-comment.php, line 176

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

sync_comments() uses a single global MySQL lock (discourse_lock) shared across all posts on the entire installation. The timeout is 0 (non-blocking), so if any post is currently syncing, all other posts silently skip their sync — no logging, no retry.

On a high-volume site publishing multiple posts per day, this creates a race condition where posts consistently lose the lock and never sync. Combined with the 10-minute sync period, a post can get permanently stuck if it loses the lock on its first few attempts and then the empty permalink issue prevents future syncs.

Suggested fix: Use a per-post lock:

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

With the corresponding release:

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

2. Double-Escaping of discourse_comments_raw

File: lib/discourse-comment.php, line 222

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

update_post_meta() uses $wpdb->prepare() internally, so wrapping the value in esc_sql() causes double-escaping. Over multiple sync cycles, quotes in the JSON body accumulate escape characters (\"\\\"\\\\\\\") until the JSON becomes unparseable.

Suggested fix:

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

Impact

These issues compound: the empty permalink prevents initial sync, the global lock prevents recovery, and the double-escaping can corrupt data for posts that do manage to sync. On our installation, 174 posts (roughly 2 months of content) were affected before we identified the root cause.

Hey there, thanks for your detailed report.

As the WP Discourse plugin didn’t change any of the implementation around the feature you’re having an issue with, let’s think about what else may have changed around then. Did you perhaps update another plugin at that time?

One initial issue you can address here is to update to the latest version of the WP Discourse plugin, 2.6.1.

Thanks. We’ll work on getting the plugin updated. For what it’s worth, we’re using it on a lot of our sites and they often have quite a lot of posts and get a lot of traffic.

Understood.

This is likely key. What happened in mid December 2025?

Remember that Wordpress is inherently a multi-party / composed system. It’s relatively easy for one part of your system to affect another part. I’m not saying that the WP Discourse plugin may not have an issue, but all other things being equal we need to understand what changed and how that change produced your issue before we can think about solutions.

I’ve updated the plugin. We’ll see if things level out. We created a custom WP CLI command to clean up the database if you want to take a look at it I sent you an invite. Thanks.