{"record":{"id":"f0a8b658086344f9","repo":"Stirling-Tools/Stirling-PDF","slug":"ocr-service-error-title","errorCode":null,"errorMessage":"OCR service error: ${title}","messagePattern":"OCR service error: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/core/hooks/tools/ocr/useOCROperation.ts","lineNumber":145,"sourceCode":"    }\n    return [new File([blob], `ocr_${base}.zip`, { type: \"application/zip\" })];\n  }\n\n  // Not a PDF: surface error details if present\n  if (!head.startsWith(\"%PDF\")) {\n    const textBuf = await blob.slice(0, 1024).arrayBuffer();\n    const text = new TextDecoder().decode(new Uint8Array(textBuf));\n    if (/error|exception|html/i.test(text)) {\n      if (text.includes(\"OCR tools\") && text.includes(\"not installed\")) {\n        throw new Error(\n          \"OCR tools (OCRmyPDF or Tesseract) are not installed on the server. Use the standard or fat Docker image instead of ultra-lite, or install OCR tools manually.\",\n        );\n      }\n      const title =\n        text.match(/<title[^>]*>([^<]+)<\\/title>/i)?.[1] ||\n        text.match(/<h1[^>]*>([^<]+)<\\/h1>/i)?.[1] ||\n        \"Unknown error\";\n      throw new Error(`OCR service error: ${title}`);\n    }\n    throw new Error(`Response is not a valid PDF. Header: \"${head}\"`);\n  }\n\n  const originalName = originalFiles[0].name;\n  return [new File([blob], originalName, { type: \"application/pdf\" })];\n};\n\n// Static configuration object (without t function dependencies)\nexport const ocrOperationConfig = defineSingleFileTool({\n  validateParams: validateOCRParameters,\n  buildFormData: buildOCRFormData,\n  toApiParams: ocrToApiParams,\n  fromApiParams: ocrFromApiParams,\n  operationType: \"ocr\",\n  endpoint: ENDPOINT,\n  defaultParameters,\n});","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/core/hooks/tools/ocr/useOCROperation.ts#L127-L163","documentation":"Thrown by the OCR response handler when the response is not a PDF/ZIP but its first 1KB matches /error|exception|html/ and does not match the OCR-tools-not-installed substring. It parses an HTML <title> or <h1> and reports it as the service error. This is a catch-all for server-side errors that came back as HTML (Spring error page) or plain text instead of a valid PDF.","triggerScenarios":"Backend threw an exception (500) and Spring rendered its default error/whitelabel page; a reverse proxy (nginx, gateway) returned its own HTML error page; the OCR endpoint hit a size/time limit and returned an HTML error; a malformed request caused a 4xx with an HTML body.","commonSituations":"Transient backend crash during OCR; reverse-proxy timeout returning HTML; the OCRmyPDF subprocess crashed and the controller forwarded the stack trace as HTML.","solutions":["Check backend logs around the time of the request for the matching exception/stack trace.","Verify the request payload (file size, language param) is within the endpoint's limits; OCRmyPDF can fail on very large or corrupt PDFs.","If the title is a proxy/gateway error (502/504), raise upstream timeouts for the OCR endpoint.","Add server-side structured error responses (JSON) so the frontend can route by status code instead of scraping HTML."],"exampleFix":"// before\nconst title = text.match(/<title[^>]*>([^<]+)<\\/title>/i)?.[1] || text.match(/<h1[^>]*>([^<]+)<\\/h1>/i)?.[1] || \"Unknown error\";\nthrow new Error(`OCR service error: ${title}`);\n\n// after — include HTTP status context for diagnosis\nthrow new Error(`OCR service error: ${title} (HTTP ${response.status} from ${ENDPOINT})`);","handlingStrategy":"try-catch","validationCode":"// Treat non-2xx explicitly before parsing the body\nif (response.status < 200 || response.status >= 300) {\n  // surface `OCR service error: HTTP ${response.status}` without scraping HTML\n}","typeGuard":null,"tryCatchPattern":"try {\n  await runOcr();\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith(\"OCR service error:\")) {\n    toast.error(e.message);\n    // surface backend logs hint to ops\n  } else throw e;\n}","preventionTips":["Check backend logs for the matching exception when this surfaces.","Verify OCR input size/params are within the endpoint's limits.","Push the backend to return structured JSON errors so the frontend can route by status code."],"tags":["ocr","backend-error","html-parsing","network"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}