From bcd723055758b33e528f07a7eac6300eba73dcb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Wed, 29 Apr 2026 20:03:40 +0200 Subject: [PATCH] fix: fall back to screenshot mode when any CDP call times out during calibration (#567) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - **Root cause**: `shouldFallbackToScreenshotAfterCalibrationError` only matched `HeadlessExperimental.beginFrame` errors. When a composition with many heavy videos (e.g. 7 videos with sparse keyframes) caused Chrome to be unresponsive in BeginFrame mode during calibration, a `Runtime.callFunctionOn timed out` or `Runtime.evaluate timed out` error was treated as an opaque failure — not a BeginFrame-mode signal. The render kept BeginFrame mode, spawned 3 workers with `captureCostMultiplier=8`, and all 3 workers also timed out initialising their sessions (0 frames captured, render fails). - **Fix**: Add `Runtime.callFunctionOn timed out` and `Runtime.evaluate timed out` to the screenshot-fallback pattern. Any CDP call timing out during the short-timeout calibration probe now routes the render into single-worker screenshot mode — the safe fallback already used for explicit BeginFrame timeouts. - **Result**: Compositions that overwhelm BeginFrame mode (reported in #566: 7 videos, 8 audios, 330-second render) now fall back cleanly and complete instead of failing with 0 frames. ## Test plan - [x] New unit test: `falls back to screenshot mode after Runtime.callFunctionOn timeout during calibration` — asserts both `Runtime.callFunctionOn timed out` and `Runtime.evaluate timed out` return `true` - [x] All existing `capture calibration safeguards` unit tests still pass - [x] Pre-commit hooks (lint, format, typecheck) pass Fixes #566 --- .../src/services/renderOrchestrator.test.ts | 17 +++++++++++++++++ .../producer/src/services/renderOrchestrator.ts | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/producer/src/services/renderOrchestrator.test.ts b/packages/producer/src/services/renderOrchestrator.test.ts index 5c524204a..746360168 100644 --- a/packages/producer/src/services/renderOrchestrator.test.ts +++ b/packages/producer/src/services/renderOrchestrator.test.ts @@ -424,6 +424,23 @@ describe("capture calibration safeguards", () => { ).toBe(true); expect(shouldFallbackToScreenshotAfterCalibrationError(new Error("ffmpeg exited"))).toBe(false); }); + + it("falls back to screenshot mode after Runtime.callFunctionOn timeout during calibration", () => { + expect( + shouldFallbackToScreenshotAfterCalibrationError( + new Error( + "Runtime.callFunctionOn timed out. Increase the 'protocolTimeout' setting in launch/connect calls for a higher timeout if needed.", + ), + ), + ).toBe(true); + expect( + shouldFallbackToScreenshotAfterCalibrationError( + new Error( + "Runtime.evaluate timed out. Increase the 'protocolTimeout' setting in launch/connect calls for a higher timeout if needed.", + ), + ), + ).toBe(true); + }); }); describe("adaptive missing-frame retry helpers", () => { diff --git a/packages/producer/src/services/renderOrchestrator.ts b/packages/producer/src/services/renderOrchestrator.ts index 9acdad7cb..271e9ef5f 100644 --- a/packages/producer/src/services/renderOrchestrator.ts +++ b/packages/producer/src/services/renderOrchestrator.ts @@ -890,7 +890,7 @@ export function isRecoverableParallelCaptureError(error: unknown): boolean { export function shouldFallbackToScreenshotAfterCalibrationError(error: unknown): boolean { const message = error instanceof Error ? error.message : String(error); - return /HeadlessExperimental\.beginFrame timed out|beginFrame probe timeout|Another frame is pending|Frame still pending|Protocol error.*HeadlessExperimental\.beginFrame/i.test( + return /HeadlessExperimental\.beginFrame timed out|beginFrame probe timeout|Another frame is pending|Frame still pending|Protocol error.*HeadlessExperimental\.beginFrame|Runtime\.callFunctionOn timed out|Runtime\.evaluate timed out/i.test( message, ); }