{"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":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"extensions/vector-db-extension/src/index.ts","lineNumber":244,"sourceCode":"    } catch (e) {\n      throw new Error(\n        `Failed to count embedding tokens: ${e instanceof Error ? e.message : String(e)}`\n      )\n    }\n    if (count <= budget || text.length <= MIN_CHUNK_SIZE_CHARS) return [text]\n    const mid = Math.floor(text.length / 2)\n    return [\n      ...(await this.splitChunkToFit(text.slice(0, mid), budget, llm)),\n      ...(await this.splitChunkToFit(text.slice(mid), budget, llm)),\n    ]\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 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":226,"sourceCodeEnd":262,"githubUrl":"https://github.com/janhq/jan/blob/7205d770c1e097c3daf35a911176410e93bc5564/extensions/vector-db-extension/src/index.ts#L226-L262","documentation":"ingestFile guards against attaching the same file twice to a thread: it lists existing attachments in the thread's collection and throws if a file with identical name AND path already exists. This is an idempotency guard to prevent duplicate chunks/embeddings for the same document.","triggerScenarios":"Calling ingestFile(threadId, file, opts) where the thread's attachment list already contains a file whose f.name === file.name and f.path === file.path — i.e. re-attaching the exact same file to the same thread.","commonSituations":"Re-running an ingest/attach script against the same thread, a UI allowing a second attach click before the first completes and persisted, or replaying a queued attachment job.","solutions":["Check the thread's existing attachments before calling ingestFile and skip files with matching name and path.","If re-ingestion is intentional (file content changed), use a distinct path or remove the existing attachment first.","Catch this error and treat it as a no-op/success if your workflow only needs the file attached once.","Use deterministic file identifiers so retries of the same attach job are recognized as duplicates."],"exampleFix":"// before\nawait ingestFile(threadId, file, opts) // throws on second call\n// after\nconst existing = await vecdb.listAttachments(collection).catch(() => [])\nif (!existing.some(f => f.name === file.name && f.path === file.path)) {\n  await ingestFile(threadId, file, opts)\n}","handlingStrategy":"validation","validationCode":"const existing = await vecdb.listAttachments(collection).catch(() => [])\nconst isDup = existing.some((f: any) => f.name === file.name && f.path === file.path)\nif (isDup) return // or update instead of ingest\nawait ingestFile(threadId, file, opts)","typeGuard":null,"tryCatchPattern":"try {\n  await ingestFile(threadId, file, opts)\n} catch (e) {\n  if (String(e).includes(\"has already been attached\")) {\n    return existingAttachmentInfo // treat as idempotent success\n  }\n  throw e\n}","preventionTips":["Track attached files per thread in application state and disable duplicate attach in the UI.","Debounce/lock attach buttons to prevent double-submission races.","Make ingest jobs idempotent by keying on (threadId, name, path)."],"tags":["duplicate","ingestion","validation"],"backgroundTag":"file-already-exists","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"}