{"record":{"id":"080e142bc2c471a0","repo":"janhq/jan","slug":"file-file-name-has-already-been-attached-to-t-080e14","errorCode":null,"errorMessage":"File '${file.name}' has already been attached to this thread","messagePattern":"File '(.+?)' has already been attached to this thread","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"extensions/vector-db-extension/src/index.ts","lineNumber":265,"sourceCode":"  private async embedTexts(texts: string[]): Promise<number[][]> {\n    const llm = this.getEmbeddingEngine()\n    if (!llm?.embed) throw new Error('llamacpp extension not available')\n\n    const res = await llm.embed(texts)\n    const data: Array<{ embedding: number[]; index: number }> = res?.data || []\n    const out: number[][] = new Array(texts.length)\n    for (const item of data) {\n      out[item.index] = item.embedding\n    }\n    return out\n  }\n\n  async ingestFile(threadId: string, file: VectorDBFileInput, opts: VectorDBIngestOptions): Promise<AttachmentFileInfo> {\n    // Check for duplicate file (same name + path)\n    const existingFiles = await vecdb.listAttachments(this.collectionForThread(threadId)).catch(() => [])\n    const duplicate = existingFiles.find((f: any) => f.name === file.name && f.path === file.path)\n    if (duplicate) {\n      throw new Error(`File '${file.name}' has already been attached to this thread`)\n    }\n\n    const text = await ragApi.parseDocument(file.path, file.type || 'application/octet-stream')\n    const chunks = await this.chunkText(text, opts.chunkSize, opts.chunkOverlap)\n    if (!chunks.length) {\n      const fi = await vecdb.createFile(this.collectionForThread(threadId), file)\n      return fi\n    }\n    const embeddings = await this.embedTexts(chunks)\n    const dimension = embeddings[0]?.length || 0\n    if (dimension <= 0) throw new Error('Embedding dimension not available')\n    await this.createCollection(threadId, dimension)\n    const fi = await vecdb.createFile(this.collectionForThread(threadId), file)\n    await vecdb.insertChunks(\n      this.collectionForThread(threadId),\n      fi.id,\n      chunks.map((t, i) => ({ text: t, embedding: embeddings[i] }))\n    )","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/janhq/jan/blob/fad3f12a147d138388a66f0d92a02b2675f65294/extensions/vector-db-extension/src/index.ts#L247-L283","documentation":"Thrown by VectorDBExtension.ingestFile() (thread-level) after listing a thread's attachments, when an attachment with the same name AND path already exists. Duplicate detection is by (name, path); a same-name file from a different path is allowed. This is the thread-scoped counterpart of error 67.","triggerScenarios":"Calling ingestFile twice for the same thread with the same file; re-attaching a document the user already added to the chat; automation re-ingesting on each message.","commonSituations":"User re-drops the same file into a thread; resume-after-error retries an already-recorded file; deduplication not performed upstream.","solutions":["Call listAttachments(threadId) and skip files whose (name, path) already exist.","Delete the existing attachment (deleteFile) before re-ingesting to force a refresh.","Dedupe the upload list by (name, path) before calling ingestFile.","Catch the error and treat it as idempotent success if re-ingest is intended."],"exampleFix":"// before\nawait vecdbExt.ingestFile(threadId, file, opts)\n\n// after\nconst existing = await vecdbExt.listAttachments(threadId)\nconst dup = existing.find((f) => f.name === file.name && f.path === file.path)\nif (dup) return dup\nawait vecdbExt.ingestFile(threadId, file, opts)","handlingStrategy":"validation","validationCode":"const existing = await vecdbExt.listAttachments(threadId)\nconst isDup = existing.some((f) => f.name === file.name && f.path === file.path)\nif (isDup) {\n  // skip, or deleteFile first to refresh\n}","typeGuard":null,"tryCatchPattern":"try {\n  await vecdbExt.ingestFile(threadId, file, opts)\n} catch (e) {\n  if (e instanceof Error && e.message.includes('already been attached')) return // idempotent\n  throw e\n}","preventionTips":["Dedupe attachments per thread by (name, path).","Reflect attached files in UI state to prevent re-add.","Delete the old attachment before re-ingesting for a refresh."],"tags":["vector-db","duplicate","thread","ingestion"],"backgroundTag":null,"analyzedSha":"fad3f12a147d138388a66f0d92a02b2675f65294","analyzedAt":"2026-08-12T20:33:47.516Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}