# Empty \`discourse\_permalink\` Causes Silent Comment Sync Failures

**URL:** <https://meta.discourse.org/t/empty-discourse-permalink-causes-silent-comment-sync-failures/395962>\
**Category:** WordPress\
**Created:** [February 12, 2026, 9:39pm UTC](https://meta.discourse.org/t/empty-discourse-permalink-causes-silent-comment-sync-failures/395962 "2026-02-12T21:39:14Z")\
**Posts on this page:** 1\
**Showing post:** 1

<div class="post-metadata">

**Author:** ![jeremyhixon](https://avatars.discourse-cdn.com/v4/letter/j/a587f6/32.png) [@jeremyhixon](https://meta.discourse.org/u/jeremyhixon)\
**Post date:** [February 12, 2026, 9:39pm UTC](https://meta.discourse.org/t/empty-discourse-permalink-causes-silent-comment-sync-failures/395962/1 "2026-02-12T21:39:14Z")

</div>

## 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](http://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](https://forum.avweb.com/t/topic-slug/12345) ) 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:

```plaintext
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

```plaintext
$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:

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

```

With the corresponding release:

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

```

### 2. Double-Escaping of `discourse_comments_raw`

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

```plaintext
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:**

```plaintext
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.

---

_[View the full topic](https://meta.discourse.org/t/empty-discourse-permalink-causes-silent-comment-sync-failures/395962)._
