{"record":{"id":"2d08005a40ac1dd9","repo":"remotion-dev/remotion","slug":"video-layer-output-was-canceled","errorCode":null,"errorMessage":"Video layer output was canceled","messagePattern":"Video layer output was canceled","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/video-matting/src/create-video-layer-output.ts","lineNumber":220,"sourceCode":"\t\t})();\n\n\t\treturn cancellationPromise;\n\t};\n\n\tconst finalize = (): Promise<VideoLayerOutput> => {\n\t\tif (cancellationRequested) {\n\t\t\treturn Promise.reject(new Error('Video layer output was canceled'));\n\t\t}\n\n\t\tif (finalizationPromise !== null) {\n\t\t\treturn finalizationPromise;\n\t\t}\n\n\t\tfinalizationPromise = (async () => {\n\t\t\ttry {\n\t\t\t\tawait output.finalize();\n\t\t\t\tif (cancellationRequested) {\n\t\t\t\t\tthrow new Error('Video layer output was canceled');\n\t\t\t\t}\n\n\t\t\t\tconst mimeType = await output.getMimeType();\n\t\t\t\tif (cancellationRequested) {\n\t\t\t\t\tthrow new Error('Video layer output was canceled');\n\t\t\t\t}\n\n\t\t\t\tawait closeOutputWritable();\n\t\t\t\tif (cancellationRequested) {\n\t\t\t\t\tthrow new Error('Video layer output was canceled');\n\t\t\t\t}\n\n\t\t\t\tfinalized = true;\n\n\t\t\t\tif (outputMode === 'writable') {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tdispose: () => Promise.resolve(),\n\t\t\t\t\t\tgetBlob: () =>","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/remotion-dev/remotion/blob/b2f4e34732f3c6c222ea029413e22dc402218cd7/packages/video-matting/src/create-video-layer-output.ts#L202-L238","documentation":"Thrown by finalize() in create-video-layer-output when the mediabunny output finished finalizing but a cancellation had been requested in the meantime. The library checks cancellationRequested after each async step of finalization so it never hands back output from an aborted render. It signals that the caller canceled the layer output (or something in the pipeline did) while finalize() was in flight.","triggerScenarios":"Calling finalize() on a video layer output while another code path has already invoked cancel() (or discard()) on it; the cancellation lands between output.finalize() and the next check.","commonSituations":"A user aborts a matting render in the UI mid-encode; a component unmounts and calls cancel() concurrently with finalize(); a timeout wrapper cancels the output while finalize is still awaiting.","solutions":["Await the cancel() promise and treat finalize()'s rejection as expected — do not retry finalize after cancel","Restructure so cancel() and finalize() are not called concurrently; finalize first, then cancel only on error","Guard with the returned finalizationPromise so only one lifecycle call runs per output","Check output.state before calling finalize; skip finalize if the output was canceled"],"exampleFix":"// before\nconst out = await layerOutput.finalize();\n// after\ntry {\n  const out = await layerOutput.finalize();\n} catch (e) {\n  if (wasCanceled) return; // cancel() was invoked, rejection is expected\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"if (typeof layerOutput.cancel === 'function') {\n  // ensure no cancel() is pending before finalizing\n}\n","typeGuard":"const canFinalize = (o: {isCanceled?: () => boolean}) => !o.isCanceled?.();\n","tryCatchPattern":"try {\n  await layerOutput.finalize();\n} catch (e) {\n  if ((e as Error).message === 'Video layer output was canceled') return;\n  throw e;\n}\n","preventionTips":["Never call cancel() and finalize() concurrently on the same output","Track a generation/epoch counter and only finalize the current generation","Await cancel() fully before deciding whether to finalize"],"tags":["cancellation","async","mediabunny"],"backgroundTag":"invalid-state-transition","analyzedSha":"b2f4e34732f3c6c222ea029413e22dc402218cd7","analyzedAt":"2026-09-09T13:27:51.281Z","contentChangedAt":"2026-09-09T13:27:51.281Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}