{"record":{"id":"fb1a85af7f7c93bb","repo":"Mintplex-Labs/anything-llm","slug":"collector-api-is-not-online-skipping-document-att","errorCode":null,"errorMessage":"Collector API is not online, skipping document attachment processing","messagePattern":"Collector API is not online, skipping document attachment processing","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/utils/chats/apiChatHandler.js","lineNumber":67,"sourceCode":"  const documentAttachments = [];\n  const imageAttachments = [];\n  for (const attachment of attachments) {\n    if (\n      attachment &&\n      attachment.contentString &&\n      attachment.mime &&\n      attachment.mime.toLowerCase() === \"application/anythingllm-document\"\n    )\n      documentAttachments.push(attachment);\n    else imageAttachments.push(attachment);\n  }\n\n  if (documentAttachments.length === 0)\n    return { parsedDocuments: [], imageAttachments };\n  const Collector = new CollectorApi();\n  const processingOnline = await Collector.online();\n  if (!processingOnline) {\n    console.warn(\n      \"Collector API is not online, skipping document attachment processing\"\n    );\n    return { parsedDocuments: [], imageAttachments };\n  }\n  if (!fs.existsSync(hotdirPath)) fs.mkdirSync(hotdirPath, { recursive: true });\n\n  const parsedDocuments = [];\n  for (const attachment of documentAttachments) {\n    try {\n      let base64Data = attachment.contentString;\n      const dataUriMatch = base64Data.match(/^data:[^;]+;base64,(.+)$/);\n      if (dataUriMatch) base64Data = dataUriMatch[1];\n\n      const buffer = Buffer.from(base64Data, \"base64\");\n      const filename = sanitizeFileName(\n        normalizePath(attachment.name || `attachment-${uuidv4()}`)\n      );\n      const filePath = normalizePath(path.join(hotdirPath, filename));","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/20f6d3546c1938bfea1ad304f58a592dddcc5948/server/utils/chats/apiChatHandler.js#L49-L85","documentation":"During chat handling, AnythingLLM splits attachments into image vs document (mime application/anythingllm-document). Document attachments must be parsed and embedded by the separate Python \"collector\" service, so the handler first calls CollectorApi.online(), which is a plain fetch against http://0.0.0.0:COLLECTOR_PORT that returns true only on res.ok. When that probe fails, this warning is printed and parsedDocuments comes back empty — the chat still completes, but the model never sees the attachment's content.","triggerScenarios":"Sending a chat request whose attachments include mime \"application/anythingllm-document\" while the collector is stopped, crashed, OOM-killed, still booting, or listening on a different port than COLLECTOR_PORT. Any non-2xx response or connection error (ECONNREFUSED) from the collector endpoint produces the same path.","commonSituations":"Docker deployments where only the server container runs (collector exited after an image pull failure or crash-loop), bare-metal Node runs without starting the Python collector, COLLECTOR_PORT mismatches between containers, or racing collector startup during the first requests after boot. Users see the assistant answer while ignoring the attached PDF/DOCX.","solutions":["Check that the collector process/container is actually running: docker ps (or ps aux | grep collector) and inspect its logs for a crash; restart it (e.g. docker compose up -d collector).","Verify the port: curl -f http://127.0.0.1:$COLLECTOR_PORT/ from the server container and confirm COLLECTOR_PORT matches the collector's real listen port and the compose port mapping.","If the collector crash-loops, read its logs (python deps, OCR/whisper models, memory) and fix the underlying error before retrying.","Re-send the chat with the attachment once the probe returns ok — the skipped documents are not retried retroactively."],"exampleFix":"// before: silently degrades — chat answers without the attached document\nconst { parsedDocuments } = await parseAttachments({ attachments: msg.attachments });\n\n// after: fail fast with an actionable error when documents are attached\nconst collectorOnline = await new CollectorApi().online();\nconst hasDocs = msg.attachments?.some(a => a.mime?.toLowerCase() === \"application/anythingllm-document\");\nif (hasDocs && !collectorOnline) {\n  throw new Error(\"Document processing is offline — start the collector and resend this chat.\");\n}","handlingStrategy":"validation","validationCode":"import { CollectorApi } from \"../utils/collectorApi\";\n\nconst collector = new CollectorApi();\nconst hasDocAttachments = (attachments = []) =>\n  attachments.some(a => a.mime?.toLowerCase() === \"application/anythingllm-document\");\n\nif (hasDocAttachments(req.body.attachments) && !(await collector.online())) {\n  return res.status(503).json({\n    error: \"Document processing is temporarily unavailable. Remove document attachments or retry shortly.\",\n  });\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Add a Docker healthcheck (curl -f http://127.0.0.1:PORT/) to the collector service and make the server depend_on it.","Keep COLLECTOR_PORT identical in server and collector environments, including compose port mappings.","Treat 'assistant ignores attached files' as the user-facing symptom of this warning and surface it in UI toasts rather than dropping context silently."],"tags":["collector","attachments","service-availability","docker","chat"],"backgroundTag":"service-unavailable","analyzedSha":"20f6d3546c1938bfea1ad304f58a592dddcc5948","analyzedAt":"2026-08-18T10:02:21.017Z","contentChangedAt":"2026-08-18T10:02:21.017Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}