From e47fdad7d0cabeeaa840cb6012597e2e387379a8 Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Mon, 26 Oct 2026 21:07:19 +0000 Subject: [PATCH] fix(producer): open a fresh segmented session when the probe has risk frames (#6434) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(producer): open a fresh segmented session when the probe has risk frames Since #6318 the gate-check probe is planned with deFrameFallback: true, so it can carry deRiskFrames. The segmented stage reuses that probe for its first session and captures every frame with captureFrameToBuffer, which cannot hand a risk frame to a screenshot session: the first one throws "frame needs screenshot capture, which this capture path cannot hand over" and the segmented route has no drawElement catch, so the render fails. Close such a probe and open a fresh segmented session instead; those are created with deFrameFallback: false and keep the whole-render screenshot fallback. — Via Co-Authored-By: Claude Opus 5.5 (1M context) * refactor(producer): name the risk-frame probe condition instead of commenting it The comment ratchet does not allow a file's comment share to rise. — Via Co-Authored-By: Claude Opus 5.5 (1M context) --------- Co-authored-by: Claude Opus 5.5 (1M context) --- .../stages/captureSegmentedStage.test.ts | 31 +++++++++++++++++++ .../render/stages/captureSegmentedStage.ts | 5 +++ 2 files changed, 36 insertions(+) diff --git a/packages/producer/src/services/render/stages/captureSegmentedStage.test.ts b/packages/producer/src/services/render/stages/captureSegmentedStage.test.ts index 3245f16c7a..359852ae46 100644 --- a/packages/producer/src/services/render/stages/captureSegmentedStage.test.ts +++ b/packages/producer/src/services/render/stages/captureSegmentedStage.test.ts @@ -315,6 +315,37 @@ describe("runCaptureSegmentedStage", () => { }); }); + it("does not capture on a probe that left frames to a screenshot session", async () => { + closeCaptureSession.mockClear(); + const probeSession = { + ...fakeStageInput({ totalFrames: 1 }).probeSession, + captureMode: "drawelement" as const, + workerEncodeEnabled: true, + deRiskFrames: new Set([2]), + }; + const capturedOn = new Set(); + const captureFrame = mock(async (session: unknown) => { + capturedOn.add(session); + return { buffer: Buffer.alloc(1) }; + }); + const result = await runCaptureSegmentedStage({ + ...fakeStageInput({ totalFrames: 6 }), + probeSession: probeSession as never, + segmentFrames: 3, + segmentDir: join(fixtureRoot, "risk-probe"), + deps: { + spawnEncoder: mock(async () => okEncoder()), + captureFrame, + concat: mock(async () => ({ success: true as const })), + closeSession: mock(async () => {}), + removeFile: () => {}, + }, + }); + expect(result.success).toBe(true); + expect(capturedOn.has(probeSession)).toBe(false); + expect(closeCaptureSession.mock.calls.map((c) => c[0])).toContain(probeSession); + }); + it("skips completed segments and still concats all of them in order", async () => { const stableDir = join(fixtureRoot, "stable"); const spawned: string[] = []; diff --git a/packages/producer/src/services/render/stages/captureSegmentedStage.ts b/packages/producer/src/services/render/stages/captureSegmentedStage.ts index b7704916f9..a4aa740b01 100644 --- a/packages/producer/src/services/render/stages/captureSegmentedStage.ts +++ b/packages/producer/src/services/render/stages/captureSegmentedStage.ts @@ -291,6 +291,11 @@ function defaultSessionFactory(input: CaptureSegmentedStageInput): SessionFactor let probeSession = input.probeSession; return { create: async () => { + const probeCannotCaptureEveryFrame = Boolean(probeSession?.deRiskFrames?.size); + if (probeSession && probeCannotCaptureEveryFrame) { + await closeCaptureSession(probeSession).catch(() => {}); + probeSession = null; + } const session = probeSession ?? (await openSegmentedSession(input)); if (probeSession) { openSegmentedSessionReuse(input, probeSession);