From f973c6a71b601a81b18de65abd54efb0cab02437 Mon Sep 17 00:00:00 2001 From: prompt.ac/@jeffrey Date: Wed, 17 Jun 2026 01:38:53 +0000 Subject: [PATCH] oven: fix grab 500 'Cannot set properties of undefined (setting status)' grabPiece() re-fetched its tracking record via activeGrabs.get(grabId) at the capturing/encoding/complete transitions. If the map entry was evicted mid- capture (cleanupStaleGrabs / clearAllActiveGrabs / dedup), get() returned undefined and `.status =` threw, surfacing as an HTTP 500 from /grab. The keep flow then fell back to using the HTML artifact as the thumbnail, which an can't render — blank thumbnails in the prepared-artifacts window. Hold a stable local grabRecord reference and use it for all status/field writes so capture completes regardless of map mutation. Co-Authored-By: Claude Opus 4.8 --- oven/grabber.mjs | 21 +++++++++++++-------- 1 file(s) changed, 13 insertion(s)(+), 8 deletion(s)(-) diff --git a/oven/grabber.mjs b/oven/grabber.mjs --- a/oven/grabber.mjs +++ b/oven/grabber.mjs @@ -2352,7 +2352,11 @@ if (source) console.log(` Source: ${source}${keepId ? ' #' + keepId : ''}`); serverLog('queue', '📋', `Grab queued: ${piece} (${format} ${width}×${height})`); // Track active grab - store original piece name (with $ if KidLisp) - activeGrabs.set(grabId, { + // Hold a stable local reference. The map entry can be evicted mid-capture by + // cleanupStaleGrabs()/clearAllActiveGrabs(); re-fetching via get() then yields + // undefined and `.status =` throws "Cannot set properties of undefined". The + // local ref survives map mutation so the capture always completes. + const grabRecord = { id: grabId, piece: piece, // Keep original with $ prefix for URL generation format, @@ -2366,14 +2370,15 @@ keepId: keepId || null, author: author || null, pieceCreatedAt: pieceCreatedAt || null, requestOrigin: requestOrigin || null, - }); + }; + activeGrabs.set(grabId, grabRecord); // Use queue to serialize capture operations (avoid parallel puppeteer sessions) // Pass metadata for queue visibility + priority scheduling return enqueueGrab(async () => { try { let result; - activeGrabs.get(grabId).status = 'capturing'; + grabRecord.status = 'capturing'; // Initialize progress state for this grab updateProgress(grabId, { @@ -2386,7 +2391,7 @@ framesCaptured: 0, framesTotal: Math.ceil((duration / 1000) * fps), author: author || null, pieceCreatedAt: pieceCreatedAt || null, - requestedAt: activeGrabs.get(grabId)?.startTime || Date.now(), + requestedAt: grabRecord.startTime || Date.now(), source: source || 'manual', requestOrigin: requestOrigin || null, }); @@ -2533,7 +2538,7 @@ } recordFrozenPiece(piece, `Dud — ${uniformCheck.reason}, still frame returned`, dudPreviewUrl); } else { - activeGrabs.get(grabId).status = 'encoding'; + grabRecord.status = 'encoding'; // Update progress: encoding stage updateProgress(grabId, { @@ -2560,8 +2565,8 @@ } } } - // Update status - const grab = activeGrabs.get(grabId); + // Update status (local ref — immune to map eviction mid-capture) + const grab = grabRecord; grab.status = 'complete'; grab.completedAt = Date.now(); grab.duration = grab.completedAt - grab.startTime; @@ -2635,7 +2640,7 @@ // Clear progress state on error clearProgress(grabId); - const grab = activeGrabs.get(grabId); + const grab = grabRecord; if (grab) { grab.status = 'failed'; grab.error = error.message; -- tangled.sh