{"record":{"id":"56cedc11a8eb5f0a","repo":"Stirling-Tools/Stirling-PDF","slug":"policy-requires-interactive-input-and-cannot-run-a","errorCode":null,"errorMessage":"policy requires interactive input and cannot run automatically","messagePattern":"policy requires interactive input and cannot run automatically","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"frontend/editor/src/proprietary/services/policyExport.ts","lineNumber":113,"sourceCode":"      view = await getPolicyRun(runId);\n    } catch {\n      continue; // transient — keep polling within the cap.\n    }\n    if (view.status === \"COMPLETED\") {\n      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,","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/proprietary/services/policyExport.ts#L95-L131","documentation":"Thrown during export-time policy enforcement when the backend policy run returns a status of WAITING_FOR_INPUT. The export path polls the policy runner via getPolicyRun() inside runToCompletion(); an interactive policy cannot be auto-resolved because there is no UI surface mid-export to capture user input, so the run is treated as a hard terminal state. The module header states export is never hard-blocked — the caller catches this and falls back to the original file with a warning toast.","triggerScenarios":"A policy configured with runOn:'export' contains an interactive step (e.g. redaction approval, classification label selection, review checkpoint). The runStoredPolicy call succeeds and starts a run, but getPolicyRun polls return status 'WAITING_FOR_INPUT' before reaching COMPLETED.","commonSituations":"Policy authored with a human-review gate that is also flagged for automatic export enforcement; backend classification engine flags content it cannot auto-decide on; policy configuration drift where an interactive policy is accidentally set to runOn export.","solutions":["Change the policy's runOn from 'export' to 'manual' so interactive steps never block the export path","Configure the policy's backend to auto-resolve interactive decisions (auto-approve / auto-redact) so it reaches COMPLETED without WAITING_FOR_INPUT","Split the policy: run the interactive portion on-demand before export, and only attach the deterministic portion to export enforcement","Ensure the export caller catches this specific error and falls back to exporting the original file with a warning toast (the module is designed for this)"],"exampleFix":"// before\nconst result = await runToCompletion(backendId, file);\nreturn result.file;\n\n// after\ntry {\n  const result = await runToCompletion(backendId, file);\n  return result.file;\n} catch (e) {\n  if (e instanceof Error && e.message.includes('interactive input')) {\n    alert(i18n.t('policies.enforcement.interactiveSkipped'), 'warning');\n    return file; // fall back to original\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"// Check if the policy has interactive steps before export\nconst policies = activeExportPolicies();\nconst hasInteractive = policies.some((p) => policyCatalogHasInteractiveSteps(p.categoryId));\nif (hasInteractive) {\n  // Warn user that interactive policies will fall back to original file\n  showInteractivePolicyWarning();\n}","typeGuard":null,"tryCatchPattern":"try {\n  const result = await runToCompletion(backendId, file);\n  enforcedFile = result.file;\n} catch (e) {\n  if (e instanceof Error && e.message.includes('interactive input')) {\n    enforcedFile = file; // fall back to original per module contract\n    showToast('policies.enforcement.interactiveSkipped', 'warning');\n  } else {\n    throw e;\n  }\n}","preventionTips":["Never set a policy with interactive steps to runOn:'export' — use 'manual' instead","Review policy configurations in the catalog for interactive flags before enabling export enforcement","Always catch runToCompletion errors in the export caller since the module contract guarantees fallback to the original file"],"tags":["policy","export","interactive","proprietary","polling"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}