Stirling-Tools/Stirling-PDF · error · Error
policy run timed out
Error message
policy run timed out
What it means
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.
Source
Thrown at frontend/editor/src/proprietary/services/policyExport.ts:118
const out = view.outputs?.[0];
if (!out) throw new Error("policy produced no output");
const blob = await downloadPolicyOutput(out.fileId, target);
// Keep the export's filename; only the bytes are the enforced result.
const enforced = new File([blob], file.name, {
type: blob.type || file.type || "application/pdf",
});
return { file: enforced, runId, target, outputs: view.outputs ?? [] };
}
if (view.status === "FAILED" || view.status === "CANCELLED") {
throw new Error(view.error || `policy run ${view.status.toLowerCase()}`);
}
if (view.status === "WAITING_FOR_INPUT") {
throw new Error(
"policy requires interactive input and cannot run automatically",
);
}
}
throw new Error("policy run timed out");
}
function enforcedFilesSummary(names: string[]): string {
if (names.length === 1) return names[0];
if (names.length === 2)
return i18n.t("policies.enforcement.summaryTwo", {
first: names[0],
second: names[1],
});
return i18n.t("policies.enforcement.summaryMore", {
first: names[0],
second: names[1],
more: names.length - 2,
});
}
/**
* Enforce every active export-policy on each PDF just before export, returningView on GitHub (pinned to 9ef20dcab8)
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
Example fix
// before
for (let i = 0; i < MAX_POLLS; i++) {
await delay(POLL_MS);
try {
view = await getPolicyRun(runId);
} catch {
continue; // burns a poll slot
}
...
}
// after
const deadline = Date.now() + MAX_POLLS * POLL_MS;
while (Date.now() < deadline) {
await delay(POLL_MS);
let view;
try {
view = await getPolicyRun(runId);
} catch {
continue; // doesn't consume a fixed slot
}
...terminal checks...
} Defensive patterns
Strategy: retry
Validate before calling
// Estimate processing time before export
const estimatedMs = estimatePolicyProcessingTime(file.size, policyComplexity);
if (estimatedMs > MAX_POLLS * POLL_MS) {
warnUserLargeFileMayTimeOut();
} Try / catch
try {
const result = await runToCompletion(backendId, file);
} catch (e) {
if (e instanceof Error && e.message.includes('timed out')) {
// Retry once — the backend run may have completed but polling expired
const view = await getPolicyRun(runId).catch(() => null);
if (view?.status === 'COMPLETED') {
return downloadAndProcessOutput(view);
}
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- policy requires interactive input and cannot run automatical
- policy produced no output
- view.error || policy run ${view.status.toLowerCase()}
- Failed to load images for pages ${missing.map((i) => i + 1).
- No pages to export
AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13).
Data as JSON: /api/errors/9fd37d1f66cae9b5.
Report an issue: GitHub.