Describe the bug
On Windows, spawning the managed ffmpeg/ffprobe binaries can fail transiently with EBUSY/ETXTBSY when the executable file is briefly locked — overwhelmingly antivirus real-time scanning a just-written or just-executed exe, occasionally a lingering handle from a concurrent sibling invocation (a single render shares one managed ffmpeg binary across many stages: media probing, frame extraction, audio mix, encode).
When that happens, the whole render fails with a bare, context-free error and there is no retry:
We observed this repeatedly in an Electron integration of the producer: a Delivery transcode step used the same managed ffmpeg.exe seconds before the render pipeline started; the render then failed on its first ffmpeg/ffprobe spawn and the user-facing export was lost. Re-running the export afterwards succeeded — confirming the condition is transient.
Root cause
All three spawn sites surface the raw child-process error without classification or retry:
packages/engine/src/utils/runFfmpeg.ts — the spawn error arrives inside the ManagedChildProcess outcome (reason: "spawn_error", raw error).
packages/producer/src/services/audioExtractor.ts (runFFmpeg) — ffmpeg.on("error", reject).
packages/producer/src/services/render/audioPadTrim.ts (runFfprobeJson) — throws outcome.error raw.
Node reports these as Error: spawn EBUSY (with .code set), so neither the user nor the logs can tell which binary, which stage, or that the condition usually clears on retry.
Expected behavior
- A transient file-lock spawn failure should be retried a few times with a short backoff — a fresh spawn almost always succeeds once the AV scan finishes.
- When retries are exhausted, the error should name the binary and the errno, and hint at the usual cause.
Environment
- Windows 11 x64 (applicable to any Windows host with real-time AV scanning)
- Producer used via an embedded runtime, ~v0.8.40
ffmpeg.exe/ffprobe.exe resolved via managed binary paths
Proposed fix
A shared engine helper, withTransientSpawnRetry, that:
- classifies
EBUSY, ETXTBSY, EAGAIN, EMFILE, ENFILE as transient file-lock errnos;
- re-runs the caller's existing spawn-and-wait flow with exponential backoff (250 ms base, up to 3 attempts) and logs each retry with the binary path;
- enriches exhausted failures with the binary path, errno, and a "transient file lock (often antivirus real-time scanning)" hint;
- wraps the caller's flow rather than the raw
spawn() call, so a healthy spawn keeps its exact synchronous timing.
A PR is on the way.
Describe the bug
On Windows, spawning the managed
ffmpeg/ffprobebinaries can fail transiently withEBUSY/ETXTBSYwhen the executable file is briefly locked — overwhelmingly antivirus real-time scanning a just-written or just-executed exe, occasionally a lingering handle from a concurrent sibling invocation (a single render shares one managed ffmpeg binary across many stages: media probing, frame extraction, audio mix, encode).When that happens, the whole render fails with a bare, context-free error and there is no retry:
We observed this repeatedly in an Electron integration of the producer: a Delivery transcode step used the same managed
ffmpeg.exeseconds before the render pipeline started; the render then failed on its first ffmpeg/ffprobe spawn and the user-facing export was lost. Re-running the export afterwards succeeded — confirming the condition is transient.Root cause
All three spawn sites surface the raw child-process error without classification or retry:
packages/engine/src/utils/runFfmpeg.ts— the spawn error arrives inside theManagedChildProcessoutcome (reason: "spawn_error", rawerror).packages/producer/src/services/audioExtractor.ts(runFFmpeg) —ffmpeg.on("error", reject).packages/producer/src/services/render/audioPadTrim.ts(runFfprobeJson) — throwsoutcome.errorraw.Node reports these as
Error: spawn EBUSY(with.codeset), so neither the user nor the logs can tell which binary, which stage, or that the condition usually clears on retry.Expected behavior
Environment
ffmpeg.exe/ffprobe.exeresolved via managed binary pathsProposed fix
A shared engine helper,
withTransientSpawnRetry, that:EBUSY,ETXTBSY,EAGAIN,EMFILE,ENFILEas transient file-lock errnos;spawn()call, so a healthy spawn keeps its exact synchronous timing.A PR is on the way.