Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision 3f32994e887da124cb2b8928840846ebefb11c87)
+++ src/services/ActivityPubService.js	(revision b1512e001281fec55506cf6cfa04d9d39784a5a2)
@@ -2849,5 +2849,5 @@
 // during a flux window, e.g. a fleet-wide update), and drops notes that are gone
 // (404/410). Bump SELFHEAL_VERSION only on a release that warrants a re-sync.
-const SELFHEAL_VERSION = 18; // v18: link previews no longer hang behind the note re-fetch (an unreachable origin skipped the whole row)
+const SELFHEAL_VERSION = 19; // v19: head-read no longer stops on an og:image mention inside inline script
 async function fetchNoteAP(url) {
   try {
Index: src/services/EmbedResolver.js
===================================================================
--- src/services/EmbedResolver.js	(revision 3f32994e887da124cb2b8928840846ebefb11c87)
+++ src/services/EmbedResolver.js	(revision b1512e001281fec55506cf6cfa04d9d39784a5a2)
@@ -231,5 +231,9 @@
       // the full string every read turns a 1MB page into quadratic work.
       const window = tail + chunk;
-      if (len >= MAX_HEAD || /<\/head>/i.test(window) || /og:image/i.test(window)) done_ = true;
+      // Stop on the real <meta property="og:image">, not on the bare string.
+      // Big sites carry "og:image" inside inline JSON long before the actual
+      // tag, and stopping there cut the page off just short of the meta block:
+      // the same near-miss as the old size cap, with a different cause.
+      if (len >= MAX_HEAD || /<\/head>/i.test(window) || /<meta[^>]{0,300}og:image/i.test(window)) done_ = true;
       tail = chunk.slice(-512);
     }
Index: test/embed-resolver.test.js
===================================================================
--- test/embed-resolver.test.js	(revision 3f32994e887da124cb2b8928840846ebefb11c87)
+++ test/embed-resolver.test.js	(revision b1512e001281fec55506cf6cfa04d9d39784a5a2)
@@ -182,2 +182,18 @@
   assert.ok(page && page.includes('og:image'), 'the head survives the cap');
 });
+
+// Regression: big sites carry the bare string "og:image" inside inline JSON long
+// before the real meta tag. Stopping the read there cut the page off just short
+// of the tags and produced no card at all.
+test('the head read does not stop on an og:image mention inside a script', async () => {
+  const { liveIO } = await import('../src/services/EmbedResolver.js');
+  const page = '<html><head><script>var cfg={"og:image":"decoy"};</script>'
+    + 'y'.repeat(3000)
+    + '<meta property="og:title" content="Echt"><meta property="og:image" content="https://x/real.png">'
+    + '</head></html>';
+  const fakeFetch = async () => ({ ok: true, headers: { get: () => '999' }, text: async () => page });
+  const io = liveIO({ safeFetch: fakeFetch, detectProvider: () => null });
+  const got = await io.getPage('https://x/p');
+  const { findOpenGraph } = await import('../src/services/EmbedResolver.js');
+  assert.equal(findOpenGraph(got).image, 'https://x/real.png', 'reads past the decoy to the real tag');
+});
