diff --git a/apps/web/__tests__/unit/finalize-desktop-recording.test.ts b/apps/web/__tests__/unit/finalize-desktop-recording.test.ts index b62ab316f7e..f5461d3d9e6 100644 --- a/apps/web/__tests__/unit/finalize-desktop-recording.test.ts +++ b/apps/web/__tests__/unit/finalize-desktop-recording.test.ts @@ -317,6 +317,30 @@ describe("durable post-publication transcription enqueue", () => { expect(mocks.claim).not.toHaveBeenCalled(); }); + it("finishes after repeated transcription queue failures", async () => { + withCurrent({ state: "verified" }); + mocks.transcribe.mockResolvedValue({ + success: false, + message: "Queue unavailable", + }); + mocks.sleep.mockImplementation(async () => { + if (mocks.transcribe.mock.calls.length >= 12) { + throw new Error("Transcription retry limit exceeded"); + } + }); + + await expect( + finalizeDesktopRecordingWorkflow({ + videoId, + userId, + generation: fixture.generation, + }), + ).resolves.toEqual({ success: true }); + expect(mocks.transcribe).toHaveBeenCalledTimes(12); + expect(mocks.fetch).not.toHaveBeenCalled(); + expect(mocks.claim).not.toHaveBeenCalled(); + }); + it.each(["unconfigured", "deleted"])( "skips %s transcription without retrying forever", async (reason) => { diff --git a/apps/web/workflows/finalize-desktop-recording.ts b/apps/web/workflows/finalize-desktop-recording.ts index 43706a882c0..2caeb0482af 100644 --- a/apps/web/workflows/finalize-desktop-recording.ts +++ b/apps/web/workflows/finalize-desktop-recording.ts @@ -85,6 +85,7 @@ const PRESIGNED_EXPIRES_SECONDS = 6 * 60 * 60; const MULTIPART_OUTPUT_PART_SIZE_BYTES = 64 * 1024 * 1024; const SOURCE_COMMIT_BATCH_MAX_CHECKPOINTS = 32; const SOURCE_COMMIT_BATCH_DURATION_MS = 15_000; +const TRANSCRIPTION_QUEUE_MAX_ATTEMPTS = 12; function getErrorMessage(error: unknown) { return error instanceof Error ? error.message : String(error); @@ -190,7 +191,7 @@ export async function finalizeDesktopRecordingWorkflow( if (retained !== "retry") return { success: false, reason: retained }; } } - for (let attempt = 0; ; attempt++) { + for (let attempt = 0; attempt < TRANSCRIPTION_QUEUE_MAX_ATTEMPTS; attempt++) { let queued: boolean; try { queued = await queueFinalizedRecordingTranscription( @@ -201,7 +202,9 @@ export async function finalizeDesktopRecordingWorkflow( queued = false; } if (queued) break; - await sleep(Math.min(15_000 * 2 ** Math.min(attempt, 5), 300_000)); + if (attempt + 1 < TRANSCRIPTION_QUEUE_MAX_ATTEMPTS) { + await sleep(Math.min(15_000 * 2 ** Math.min(attempt, 5), 300_000)); + } } return mediaServerUnavailable ? { success: false, reason: "media-server-unconfigured" } @@ -780,7 +783,7 @@ async function queueFinalizedRecordingTranscription( ); if (result.success) return true; console.warn( - "[finalizeDesktopRecordingWorkflow] Transcription enqueue will retry", + "[finalizeDesktopRecordingWorkflow] Transcription enqueue failed", { videoId, message: result.message }, ); } catch (error) {