Index: src/services/Scheduler.js
===================================================================
--- src/services/Scheduler.js	(revision 5a6a457ca2bf625de84ec03d20f7757480337fde)
+++ src/services/Scheduler.js	(revision 3dd99d358789ab51bb5361e4c9a6beaf3c1857be)
@@ -24,9 +24,12 @@
       "UPDATE posts SET status = 'published', published_at = COALESCE(published_at, publish_at, CURRENT_TIMESTAMP) WHERE id = ?"
     );
+    const ftsDel = db.prepare('DELETE FROM posts_fts WHERE post_id = ?');
     const fts = db.prepare('INSERT INTO posts_fts(content, title, author, post_id) VALUES (?, ?, ?, ?)');
     const siteStmt = db.prepare('SELECT * FROM sites WHERE id = ?');
     for (const p of due) {
       upd.run(p.id);
-      try { fts.run(HtmlSanitizerService.toPlainText(p.content || ''), p.title || '', p.username || '', p.id); } catch { /* FTS failure is non-fatal */ }
+      // Delete-before-insert so a re-scheduled (previously published) post doesn't
+      // get a duplicate FTS row → duplicate search hits.
+      try { ftsDel.run(p.id); fts.run(HtmlSanitizerService.toPlainText(p.content || ''), p.title || '', p.username || '', p.id); } catch { /* FTS failure is non-fatal */ }
       // ActivityPub: federate the now-published post to followers.
       if (!p.fan_only) {
