{"record":{"id":"91c2e99895f805ef","repo":"janhq/jan","slug":"failed-to-resolve-ingested-attachment-id","errorCode":null,"errorMessage":"Failed to resolve ingested attachment id","messagePattern":"Failed to resolve ingested attachment id","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web-app/src/services/uploads/default.ts","lineNumber":30,"sourceCode":"    return { id: ulid() }\n  }\n\n  async ingestFileAttachment(threadId: string, attachment: Attachment): Promise<UploadResult> {\n    if (attachment.type !== 'document') throw new Error('ingestFileAttachment: attachment is not document')\n    const ext = ExtensionManager.getInstance().get<RAGExtension>(ExtensionTypeEnum.RAG)\n    if (!ext?.ingestAttachments) throw new Error('RAG extension not available')\n    const res: IngestAttachmentsResult = await ext.ingestAttachments(threadId, [\n      { path: attachment.path!, name: attachment.name, type: attachment.fileType, size: attachment.size },\n    ])\n    const files = res.files\n    if (Array.isArray(files) && files[0]?.id) {\n      return {\n        id: files[0].id,\n        size: typeof files[0].size === 'number' ? Number(files[0].size) : undefined,\n        chunkCount: typeof files[0].chunk_count === 'number' ? Number(files[0].chunk_count) : undefined,\n      }\n    }\n    throw new Error('Failed to resolve ingested attachment id')\n  }\n\n  async ingestFileAttachmentForProject(projectId: string, attachment: Attachment): Promise<UploadResult> {\n    if (attachment.type !== 'document') throw new Error('ingestFileAttachmentForProject: attachment is not document')\n    const ext = ExtensionManager.getInstance().get<RAGExtension>(ExtensionTypeEnum.RAG)\n    if (!ext?.ingestAttachmentsForProject) throw new Error('RAG extension does not support project-level ingestion')\n    const res: IngestAttachmentsResult = await ext.ingestAttachmentsForProject(projectId, [\n      { path: attachment.path!, name: attachment.name, type: attachment.fileType, size: attachment.size },\n    ])\n    const files = res.files\n    if (Array.isArray(files) && files[0]?.id) {\n      return {\n        id: files[0].id,\n        size: typeof files[0].size === 'number' ? Number(files[0].size) : undefined,\n        chunkCount: typeof files[0].chunk_count === 'number' ? Number(files[0].chunk_count) : undefined,\n      }\n    }\n    throw new Error('Failed to resolve ingested attachment id')","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/janhq/jan/blob/7205d770c1e097c3daf35a911176410e93bc5564/web-app/src/services/uploads/default.ts#L12-L48","documentation":"After delegating to the RAG extension's ingestAttachments, the service expects a non-empty result.files array whose first entry has an id. If the extension returns no files, an empty array, or a first file without an id, the service cannot determine the ingested attachment's id and throws this error.","triggerScenarios":"ext.ingestAttachments resolves with res.files being undefined, an empty array, or files[0] lacking an id field — e.g. the backend accepted the upload but returned no file record.","commonSituations":"Backend ingestion silently failed or returned a partial response; a @janhq/core version changed IngestAttachmentsResult shape (files renamed or id renamed); the ingested document produced zero stored files.","solutions":["Log/res.inspect the raw IngestAttachmentsResult from the RAG extension to see what was actually returned","Verify the backend ingestion endpoint actually persists the file and returns its id","Check for a @janhq/core API change in IngestAttachmentsResult (files[].id) and update parsing if the shape moved"],"exampleFix":"// before\nconst res = await ext.ingestAttachments(threadId, [payload])\nconst result = { id: res.files![0].id }\n// after\nconst res = await ext.ingestAttachments(threadId, [payload])\nif (!res?.files?.length || !res.files[0]?.id) {\n  throw new Error(`Ingestion returned no file id for ${payload.name}: ${JSON.stringify(res)}`)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"const hasIngestedFile = (res: IngestAttachmentsResult | undefined): res is IngestAttachmentsResult & { files: [ { id: string } & Record<string, unknown>, ...unknown[] ] } =>\n  Array.isArray(res?.files) && res.files.length > 0 && typeof res.files[0]?.id === 'string'","tryCatchPattern":"try {\n  const result = await uploads.ingestFileAttachment(threadId, att)\n  return result\n} catch (e) {\n  if (e instanceof Error && e.message === 'Failed to resolve ingested attachment id') {\n    logIngestionDebug(att, e) // capture raw extension response upstream\n    throw new Error(`Document ${att.name} was ingested but no file id was returned`)\n  }\n  throw e\n}","preventionTips":["Log the raw IngestAttachmentsResult in development to catch backend schema drift early","Assert IngestAttachmentsResult.files[0].id in integration tests against the real backend","Treat empty ingestion results as explicit errors in the extension layer with actionable messages"],"tags":["rag","empty-result","api-response"],"backgroundTag":"empty-result-set","analyzedSha":"7205d770c1e097c3daf35a911176410e93bc5564","analyzedAt":"2026-09-17T14:27:30.100Z","contentChangedAt":"2026-09-17T14:27:30.100Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}