From 3f1c0449405d3229f821cc95e395e1b449d02ca6 Mon Sep 17 00:00:00 2001 From: Rob K Date: Fri, 7 Aug 2026 09:30:19 +0100 Subject: [PATCH] Return no thumbnailPath when the image thumbnail write fails deriveMediaMetadata assigned thumbnailPath before sharp wrote the file, so a failed write (corrupt image, disk error) returned a name for a file that was never created. Ingest then stored that phantom thumbnail_path and the dashboard requested it forever as a broken image. Assign only after the write succeeds; the video branch already nulled its path on failure. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0131RYmVh8ePEhparD3mXBhU --- server/lib/content-ingest.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server/lib/content-ingest.js b/server/lib/content-ingest.js index fe1063a..6a23c56 100644 --- a/server/lib/content-ingest.js +++ b/server/lib/content-ingest.js @@ -48,12 +48,16 @@ async function deriveMediaMetadata(sourcePath, filepath, mime) { const metadata = await sharp(sourcePath).metadata(); // #170: honor EXIF orientation so a portrait photo isn't stored as landscape. ({ width, height } = imageDisplayDims(metadata)); - thumbnailPath = `thumb_${filepath}`; + // Assign thumbnailPath only AFTER the write succeeds: a sharp failure used to + // return the already-assigned name for a file that was never written, storing a + // phantom thumbnail_path that the UI then requests forever as a broken image. + const thumbName = `thumb_${filepath}`; await sharp(sourcePath) .rotate() // #170: auto-orient per EXIF (and strip the tag) so the thumbnail matches .resize(config.thumbnailWidth) .jpeg({ quality: 70 }) - .toFile(path.join(config.contentDir, thumbnailPath)); + .toFile(path.join(config.contentDir, thumbName)); + thumbnailPath = thumbName; } else if (mime.startsWith('video/')) { try { const { execFileSync } = require('child_process');