{"record":{"id":"20527e761b7fdf6c","repo":"Stirling-Tools/Stirling-PDF","slug":"failed-to-export-pdf","errorCode":null,"errorMessage":"Failed to export PDF","messagePattern":"Failed to export PDF","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/core/components/viewer/EmbedPdfViewer.tsx","lineNumber":629,"sourceCode":"        (redactionTrackerRef.current?.getPendingCount() ?? 0) > 0;\n\n      // Mark redactions as applied BEFORE committing, so the button stays enabled during the save process\n      // This ensures the button doesn't become disabled when pendingCount becomes 0\n      if (hadPendingRedactions || redactionsApplied) {\n        setRedactionsApplied(true);\n      }\n\n      if (hadPendingRedactions) {\n        console.log(\"[Viewer] Committing pending redactions before export\");\n        redactionTrackerRef.current?.commitAllPending();\n        // Give a small delay for the commit to process\n        await new Promise((resolve) => setTimeout(resolve, 100));\n      }\n\n      // Step 1: Export PDF with annotations using EmbedPDF\n      const arrayBuffer = await exportActions.saveAsCopy();\n      if (!arrayBuffer) {\n        throw new Error(\"Failed to export PDF\");\n      }\n\n      // Step 2: Convert ArrayBuffer to File\n      const blob = new Blob([arrayBuffer], { type: \"application/pdf\" });\n      const filename = currentFile.name || \"document.pdf\";\n      const file = new File([blob], filename, { type: \"application/pdf\" });\n\n      // Step 3: Create StirlingFiles and stubs for version history\n      // Only consume the current file, not all active files\n      const currentFileId = currentFileStableId;\n      if (!currentFileId) throw new Error(\"Current file ID not found\");\n\n      const parentStub = selectors.getStirlingFileStub(currentFileId);\n      if (!parentStub) throw new Error(\"Parent stub not found\");\n\n      const { stirlingFiles, stubs } = await createStirlingFilesAndStubs(\n        [file],\n        parentStub,","sourceCodeStart":611,"sourceCodeEnd":647,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/core/components/viewer/EmbedPdfViewer.tsx#L611-L647","documentation":"Thrown in EmbedPdfViewer's export workflow when exportActions.saveAsCopy() returns a falsy value (null, undefined, or empty). saveAsCopy() is an EmbedPDF SDK method that serializes the currently loaded document (with annotations and redactions) into an ArrayBuffer. A null return means the SDK could not produce a valid PDF byte stream.","triggerScenarios":"The EmbedPDF viewer has not finished loading the document, the document is in a corrupted state after redaction commits, the EmbedPDF session/license expired, or the SDK encountered an internal serialization error that it reported via null rather than throwing.","commonSituations":"User clicks export immediately after loading a large PDF before EmbedPDF finishes initialization. Pending redactions were committed (commitAllPending) but the internal state is inconsistent. EmbedPDF evaluation/session token expired mid-session.","solutions":["Disable the export button until EmbedPDF signals the document is fully loaded (track a ready state).","Check arrayBuffer.byteLength > 0 in addition to truthiness — an empty buffer is also invalid.","Retry saveAsCopy() once after a short delay, as transient EmbedPDF state issues can self-resolve.","Log exportActions state (session validity, document loaded flag) when the null is hit to diagnose the SDK's internal cause."],"exampleFix":"// before\nconst arrayBuffer = await exportActions.saveAsCopy();\nif (!arrayBuffer) {\n  throw new Error(\"Failed to export PDF\");\n}\n\n// after\nconst arrayBuffer = await exportActions.saveAsCopy();\nif (!arrayBuffer || arrayBuffer.byteLength === 0) {\n  throw new Error(\n    \"EmbedPDF could not export the document. Ensure the document is fully loaded and try again.\"\n  );\n}","handlingStrategy":"try-catch","validationCode":"// Check EmbedPDF document ready state before export\nif (!embedPdfReady || !exportActions) {\n  showAlert('Document is still loading. Please wait before exporting.');\n  return;\n}\n// Validate the export action exists\nif (typeof exportActions.saveAsCopy !== 'function') {\n  throw new Error('EmbedPDF export is not available in this session.');\n}","typeGuard":"function isValidPdfBuffer(data: unknown): data is ArrayBuffer {\n  return data instanceof ArrayBuffer && data.byteLength > 0;\n}","tryCatchPattern":"try {\n  const arrayBuffer = await exportActions.saveAsCopy();\n  if (!arrayBuffer || arrayBuffer.byteLength === 0) {\n    // Retry once after short delay\n    await new Promise(r => setTimeout(r, 200));\n    const retry = await exportActions.saveAsCopy();\n    if (!retry) throw new Error('EmbedPDF export failed after retry.');\n    return retry;\n  }\n} catch (error) {\n  setErrorMessage(`PDF export failed: ${error instanceof Error ? error.message : 'unknown error'}`);\n}","preventionTips":["Track EmbedPDF document-loaded state and disable export until ready.","Check exportActions object is fully initialized before calling saveAsCopy.","Validate arrayBuffer.byteLength > 0, not just truthiness.","Test export with documents that have pending redactions separately from clean documents."],"tags":["pdf","embedpdf","export","viewer","sdk"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}