{"record":{"id":"8cfd9d16c7174e74","repo":"janhq/jan","slug":"ingestimage-attachment-is-not-image","errorCode":null,"errorMessage":"ingestImage: attachment is not image","messagePattern":"ingestImage: attachment is not image","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web-app/src/services/uploads/default.ts","lineNumber":9,"sourceCode":"import type { UploadsService, UploadResult } from './types'\nimport type { Attachment } from '@/types/attachment'\nimport { ulid } from 'ulidx'\nimport { ExtensionManager } from '@/lib/extension'\nimport { ExtensionTypeEnum, type RAGExtension, type IngestAttachmentsResult } from '@janhq/core'\n\nexport class DefaultUploadsService implements UploadsService {\n  async ingestImage(_threadId: string, attachment: Attachment): Promise<UploadResult> {\n    if (attachment.type !== 'image') throw new Error('ingestImage: attachment is not image')\n    // Placeholder upload flow; swap for real API call when backend is ready\n    await new Promise((r) => setTimeout(r, 100))\n    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,","sourceCodeStart":1,"sourceCodeEnd":27,"githubUrl":"https://github.com/janhq/jan/blob/7205d770c1e097c3daf35a911176410e93bc5564/web-app/src/services/uploads/default.ts#L1-L27","documentation":"DefaultUploadsService.ingestImage is a single-purpose method that only accepts attachments whose type is exactly 'image'. It throws this error immediately when handed an Attachment with any other type (e.g. 'document'). The check is a guard in front of a placeholder upload flow that returns a generated ulid.","triggerScenarios":"Calling uploadsService.ingestImage(threadId, attachment) with an attachment whose attachment.type is not 'image' — e.g. passing a document/PDF attachment or an attachment whose type was never set during file selection.","commonSituations":"A caller routes all attachments to ingestImage without inspecting type first; a file picker produces attachments with type inferred incorrectly; a refactor changed the Attachment type union and the caller's mapping was not updated.","solutions":["Check attachment.type === 'image' before calling ingestImage and route non-image attachments to ingestFileAttachment instead","Fix the code that constructs the Attachment so image files get type 'image'","If the call site legitimately receives mixed attachments, branch on type rather than calling ingestImage unconditionally"],"exampleFix":"// before\nconst result = await uploads.ingestImage(threadId, attachment)\n// after\nconst result = attachment.type === 'image'\n  ? await uploads.ingestImage(threadId, attachment)\n  : await uploads.ingestFileAttachment(threadId, attachment)","handlingStrategy":"type-guard","validationCode":"if (attachment.type !== 'image') { console.warn('Skipping non-image attachment', attachment.name); return }","typeGuard":"const isImageAttachment = (a: Attachment): a is Attachment & { type: 'image' } => a.type === 'image'","tryCatchPattern":"try {\n  const result = await uploads.ingestImage(threadId, att)\n} catch (e) {\n  if (e instanceof Error && e.message.includes('attachment is not image')) {\n    return uploads.ingestFileAttachment(threadId, att)\n  }\n  throw e\n}","preventionTips":["Always dispatch on attachment.type before choosing an ingest method","Add a discriminated-union type for Attachment so TypeScript forces narrowing on type","Validate attachment.type at the file-picker boundary when the Attachment is first constructed"],"tags":["type-guard","uploads","invalid-argument"],"backgroundTag":"invalid-argument-value","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"}