{"record":{"id":"9fd37d1f66cae9b5","repo":"Stirling-Tools/Stirling-PDF","slug":"policy-run-timed-out","errorCode":null,"errorMessage":"policy run timed out","messagePattern":"policy run timed out","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/proprietary/services/policyExport.ts","lineNumber":118,"sourceCode":"      const out = view.outputs?.[0];\n      if (!out) throw new Error(\"policy produced no output\");\n      const blob = await downloadPolicyOutput(out.fileId, target);\n      // Keep the export's filename; only the bytes are the enforced result.\n      const enforced = new File([blob], file.name, {\n        type: blob.type || file.type || \"application/pdf\",\n      });\n      return { file: enforced, runId, target, outputs: view.outputs ?? [] };\n    }\n    if (view.status === \"FAILED\" || view.status === \"CANCELLED\") {\n      throw new Error(view.error || `policy run ${view.status.toLowerCase()}`);\n    }\n    if (view.status === \"WAITING_FOR_INPUT\") {\n      throw new Error(\n        \"policy requires interactive input and cannot run automatically\",\n      );\n    }\n  }\n  throw new Error(\"policy run timed out\");\n}\n\nfunction enforcedFilesSummary(names: string[]): string {\n  if (names.length === 1) return names[0];\n  if (names.length === 2)\n    return i18n.t(\"policies.enforcement.summaryTwo\", {\n      first: names[0],\n      second: names[1],\n    });\n  return i18n.t(\"policies.enforcement.summaryMore\", {\n    first: names[0],\n    second: names[1],\n    more: names.length - 2,\n  });\n}\n\n/**\n * Enforce every active export-policy on each PDF just before export, returning","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/proprietary/services/policyExport.ts#L100-L136","documentation":"Thrown when the policy enforcement poll loop exhausts all MAX_POLLS (75) iterations at POLL_MS (2000ms) — roughly 150 seconds / 2.5 minutes — without the backend run reaching a terminal status (COMPLETED, FAILED, CANCELLED, or WAITING_FOR_INPUT). Each getPolicyRun() call that throws is silently swallowed with `continue`, which still consumes a poll slot, so transient network errors accelerate budget exhaustion.","triggerScenarios":"The backend policy run stays in PENDING or RUNNING past the 75-poll window. This happens when the PDF is very large and processing exceeds ~2.5 minutes, when getPolicyRun repeatedly throws transient errors (each catch{continue} burns a cycle), or when the backend policy service is overloaded/stuck.","commonSituations":"Processing a very large PDF (100GB-class) through a compute-heavy policy; backend policy pod restarted mid-run leaving the run orphaned; network latency or intermittent 5xx from the policy API eating poll cycles; policy service under load in production.","solutions":["Check the backend policy service logs for stuck or long-running policy executions","If large-file processing legitimately exceeds 2.5 min, increase MAX_POLLS or POLL_MS constants","Decouple the transient-error budget from the time budget so getPolicyRun failures don't consume poll slots (e.g. retry the fetch within a cycle before advancing i)","Reduce the input PDF size or split the operation so individual policy runs complete faster"],"exampleFix":"// before\nfor (let i = 0; i < MAX_POLLS; i++) {\n  await delay(POLL_MS);\n  try {\n    view = await getPolicyRun(runId);\n  } catch {\n    continue; // burns a poll slot\n  }\n  ...\n}\n\n// after\nconst deadline = Date.now() + MAX_POLLS * POLL_MS;\nwhile (Date.now() < deadline) {\n  await delay(POLL_MS);\n  let view;\n  try {\n    view = await getPolicyRun(runId);\n  } catch {\n    continue; // doesn't consume a fixed slot\n  }\n  ...terminal checks...\n}","handlingStrategy":"retry","validationCode":"// Estimate processing time before export\nconst estimatedMs = estimatePolicyProcessingTime(file.size, policyComplexity);\nif (estimatedMs > MAX_POLLS * POLL_MS) {\n  warnUserLargeFileMayTimeOut();\n}","typeGuard":null,"tryCatchPattern":"try {\n  const result = await runToCompletion(backendId, file);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('timed out')) {\n    // Retry once — the backend run may have completed but polling expired\n    const view = await getPolicyRun(runId).catch(() => null);\n    if (view?.status === 'COMPLETED') {\n      return downloadAndProcessOutput(view);\n    }\n  }\n  throw e;\n}","preventionTips":["Monitor backend policy service health so stuck runs are caught before the poll budget expires","Consider switching from fixed MAX_POLLS to a deadline-based loop so transient getPolicyRun errors don't consume time budget","For very large PDFs, warn the user that policy enforcement may take longer than the default 2.5-minute window"],"tags":["policy","timeout","polling","export","proprietary"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}