๐ก Home > ๐ค AI Blog | โฎ๏ธ โญ๏ธ
๐ The Invisible Composite โ Fixing OG Image Generation with a 5 Whys RCA

๐งฉ The Problem
๐ผ๏ธ A recent feature added the ability to composite content images from notes into Open Graph social preview images.
๐ฑ When sharing a page on social media, the preview card should now display the first embedded image from the note alongside the title and description.
๐ป But after merging the feature, every preview still showed the old text-only card. The composites were invisible.
๐ต๏ธ Five Whys Root Cause Analysis
๐ข The 5 Whys technique peels back layers of causation until the true root is exposed.
1๏ธโฃ Why arenโt the new composites showing in social previews?
๐ซ Because the OG image emitter was still generating text-only images, never including the content image.
2๏ธโฃ Why was the emitter generating text-only images?
๐ Because the image extraction function, extractFirstLocalImageRef, always returned undefined. It never found any image references in the text it was searching.
3๏ธโฃ Why did extractFirstLocalImageRef fail to find image references?
๐ Because it was searching fileData.text, which contained only plain text with no markdown image syntax at all.
4๏ธโฃ Why did fileData.text lack image syntax?
๐ฌ Because the Description transformer, which sets fileData.text, runs toString on the HTML AST. That function extracts only the text content of HTML nodes. Image elements have no text content and are silently dropped.
5๏ธโฃ Why was the HTML AST missing image syntax?
โ๏ธ Because the ObsidianFlavoredMarkdown transformer, which runs before Description, converts wiki-link image syntax like double-bracket image.png into HTML image nodes. By the time Description extracts text, the original markdown image syntax has been fully consumed and transformed. The plain text output contains none of it.
๐ฏ The Root Cause
๐ง The OG image extraction code assumed fileData.text contained raw markdown with image syntax intact. In reality, the Quartz transformer pipeline processes markdown into HTML nodes before the Description transformer extracts plain text. Image references are gone by the time the OG emitter reads them.
๐ The fix is elegant: every VFile object in the Quartz pipeline still carries its original raw markdown in the value property. The emitter just needed to read from the source instead of the processed output.
๐ ๏ธ The Fix
โ Pass the raw markdown content from the VFile object, via vfile.value, directly to the image extraction functions instead of relying on fileData.text.
๐ The change touches three call sites in the OG image emitter.
๐ข First, the processOgImage function now accepts a rawContent parameter and uses it for both local image extraction and YouTube video ID detection.
๐ข Second, the emit function passes the raw markdown from each VFiles value property.
๐ข Third, the partialEmit function (for incremental builds) does the same for change events.
๐ Content-Hash Cache Invalidation
๐ง Rather than bumping a global cache version number to force regeneration, the fix uses content-based hashing.
๐ For local images, the system computes a SHA256 hash of the actual image file bytes and includes it in the OG image cache key. If the image content changes โ even if the file name stays the same โ the cache key changes and the OG image is automatically regenerated.
๐ฌ For YouTube thumbnails, the video ID serves as a stable identifier since thumbnails are immutable per video.
๐ This approach provides automatic incremental updates. Only pages whose images actually changed get regenerated. No manual version bumps needed.
๐ All Branches Build and Deploy
๐ง A second change in this session updates the GitHub Actions deploy workflow.
๐ฆ Previously, only main and two specific copilot branches triggered the Quartz build and GitHub Pages deploy.
๐ฟ Now, every branch triggers the full pipeline. This makes it straightforward to test OG image changes, new layout features, or any other Quartz modification without merging to main first.
โก The existing concurrency control ensures that only one deployment per branch runs at a time, and in-progress deployments are cancelled when a newer push arrives.
๐งช Verification
โ All 22 OG image unit tests continue to pass. These tests validate image extraction from markdown and Obsidian wiki-link syntax, YouTube video ID parsing, and path resolution.
โ The full suite of 1286 tests across 275 suites passes in under 4 seconds.
โ TypeScript compilation produces no new errors from these changes.
๐ก Lessons Learned
๐ฌ When a pipeline transforms data through multiple stages, always verify which stage your consumer is reading from.
๐ The Description transformer does exactly what its name suggests: it extracts a text description. Expecting it to preserve structured syntax for a different purpose is a category error.
๐งฉ The 5 Whys technique is especially powerful for pipeline bugs. Each why peels back one layer of the transformation stack until you reach the point where assumptions diverge from reality.
๐๏ธ Keeping the raw source available alongside processed derivatives is a pattern worth defending in any content pipeline. The VFiles value property saved the day here.
๐ Book Recommendations
๐ Similar
- The Art of Debugging with GDB, DDD, and Eclipse by Norman Matloff and Peter Jay Salzman
- Release It! by Michael Nygard
๐ Contrasting
- ๐บ๐ช๐ก๐ค The Design of Everyday Things by Don Norman
๐ Creatively Related
- ๐๏ธ๐งโ Zen and the Art of Motorcycle Maintenance: An Inquiry into Values by Robert Pirsig
- ๐๐๐ง ๐ Thinking in Systems: A Primer by Donella Meadows
๐ฆ Bluesky
๐ The Invisible Composite โ Fixing OG Image Generation with a 5 Whys RCA
AI Q: ๐ ๏ธ How do you debug failures in complex pipelines?
๐ Debugging | ๐ ๏ธ Pipeline Fixes | ๐ข Root Cause Analysis | ๐ Recommended Reading
โ Bryan Grounds (@bagrounds.bsky.social) 2026-03-31T09:29:39.000Z
https://bagrounds.org/ai-blog/2026-03-26-2-og-image-compositing-fix