fix(producer): disk-headroom gate before hdr raw frame pre-extraction#2256
Conversation
miga-heygen
left a comment
There was a problem hiding this comment.
#2256 — disk-headroom gate before hdr raw frame pre-extraction
Verdict: LGTM 🟢
Prevents HDR pre-extraction from filling the disk: estimateHdrExtractionBytes computes 6 bytes/pixel × frame area × frame count across HDR videos, assertHdrExtractionDiskHeadroom checks via statfsSync and throws if >90% of free space, warns >10 GB. Error message suggests --sdr as the escape hatch. statfsSync failures (unsupported platform) skip the gate silently — only the headroom violation itself propagates.
The re-throw pattern in the catch block works correctly: throw inside try is caught by the same catch, which checks err.message.includes("HDR pre-extraction needs") and re-throws only that. All other errors (statfsSync failures) are swallowed. Slightly unusual shape but functionally correct. Test covers the estimation math.
One note: the diff includes tracked node_modules symlink changes (puppeteer 24.43.1 → 25.3.0). These shouldn't be in the index — packages/producer/node_modules/ is a pre-existing tracked artifact that #2144 (in the other stack) would catch. Not a blocker for this PR.
Review by Miga
miguel-heygen
left a comment
There was a problem hiding this comment.
Blocker: the PR diff includes tracked package-manager artifacts under packages/producer/node_modules/ (.bin/puppeteer, puppeteer, puppeteer-core symlinks) that point at a different Puppeteer/Bun installation. These are unrelated generated changes and make the branch non-hermetic. Remove them from the diff and add/verify ignore protection; keep only the source gate/tests. Re-run CI after cleanup.
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Reviewed at dec7c23.
Right layer, right escape hatch, right pre-flight ordering (throw BEFORE FFmpeg starts writing rather than mid-extraction). The byte math is straightforward, tests cover single-video math, multi-video accumulation, and negative-duration clamping.
Puppeteer symlink resync to 25.3.0 (semver-satisfies the lockfile's ^25.2.1) matches the CJS→ESM layout change in the bin symlink (lib/cjs/puppeteer/... → lib/puppeteer/...). Unrelated to the gate but was blocking tsc --declaration — reasonable to bundle rather than land a stale-lockfile follow-up.
Two things, both non-blocking:
Nit — rethrow discriminator is fragile. assertHdrExtractionDiskHeadroom at packages/producer/src/services/render/stages/captureHdrResources.ts:145-147 uses err.message.includes("HDR pre-extraction needs") to rethrow the headroom violation while swallowing statfsSync errors. Works today, but couples the discriminator to the exact error prose — a future message tweak silently fails open (statfs unsupported takes the same code path as headroom violation → the gate goes quiet). Cleaner shape:
let freeBytes: number | null = null;
try {
const stat = statfsSync(framesDir);
freeBytes = stat.bavail * stat.bsize;
} catch { return; } // statfs unsupported → skip gate silently
// … now perform the check outside the try, throw plainly.Question — observability of the reject path. Right now the gate throws with --sdr guidance; the throw propagates through extractHdrVideoFrames and shows up as a render failure. Is there a plan (either here or via Miguel's #2220 stalled-stage exposure) to emit a specific "hdr_extraction_disk_headroom_rejected" event before the throw? Distinguishing "user hit our pre-flight gate" from "FFmpeg died mid-write with ENOSPC" in aggregate telemetry is what lets you tune HDR_EXTRACTION_HEADROOM_FRACTION empirically.
LGTM from my side.
dec7c23 to
a9424d5
Compare
…l flow to error-string matching Addresses non-blocking review feedback on #2256: the previous shape used err.message.includes(...) inside a catch to distinguish a real headroom violation from a statfsSync failure — a future message tweak would silently fail open (statfs-unsupported and headroom-violation would take the same code path). Now statfsSync failure returns early (skip the gate) and the headroom check/throw happens in plain control flow outside any try/catch.
0e69871 to
c20e202
Compare
f3c3a15 to
3dc08c0
Compare
…l flow to error-string matching Addresses non-blocking review feedback on #2256: the previous shape used err.message.includes(...) inside a catch to distinguish a real headroom violation from a statfsSync failure — a future message tweak would silently fail open (statfs-unsupported and headroom-violation would take the same code path). Now statfsSync failure returns early (skip the gate) and the headroom check/throw happens in plain control flow outside any try/catch.

What
Adds a disk-headroom gate before HDR raw-frame pre-extraction.
estimateHdrExtractionBytes()computes the total rgb48le bytes the extraction will write (6 bytes/px × frame area × frame count);assertHdrExtractionDiskHeadroom()checks it against free space at the frames dir and throws with the--sdrescape hatch before FFmpeg starts writing, warning above 10 GB either way.Why
Wild report: a plain iPhone HLG clip auto-detected as HDR triggered 16-bit raw-frame pre-extraction that filled a near-full disk before the user found
--sdr; the composition used no HDR-specific features, so auto-detection was overeager and gave no warning.Validation
3 new unit tests (
estimateHdrExtractionBytes) covering byte math, multi-video accumulation, and negative-duration clamping. Full producer build clean.Review history
statfsSyncfailure viaerr.message.includes(...)) coupled control flow to exact error prose — a future message tweak would have silently failed open. Restructured sostatfsSyncfailure returns early (skip the gate) and the headroom check/throw happens in plain control flow, no string matching.🤖 Generated with Claude Code