janhq/jan · error · Error

File '${file.name}' has already been attached to this thread

Error message

File '${file.name}' has already been attached to this thread

What it means

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.

Source

Thrown at extensions/vector-db-extension/src/index.ts:265

  private async embedTexts(texts: string[]): Promise<number[][]> {
    const llm = this.getEmbeddingEngine()
    if (!llm?.embed) throw new Error('llamacpp extension not available')

    const res = await llm.embed(texts)
    const data: Array<{ embedding: number[]; index: number }> = res?.data || []
    const out: number[][] = new Array(texts.length)
    for (const item of data) {
      out[item.index] = item.embedding
    }
    return out
  }

  async ingestFile(threadId: string, file: VectorDBFileInput, opts: VectorDBIngestOptions): Promise<AttachmentFileInfo> {
    // Check for duplicate file (same name + path)
    const existingFiles = await vecdb.listAttachments(this.collectionForThread(threadId)).catch(() => [])
    const duplicate = existingFiles.find((f: any) => f.name === file.name && f.path === file.path)
    if (duplicate) {
      throw new Error(`File '${file.name}' has already been attached to this thread`)
    }

    const text = await ragApi.parseDocument(file.path, file.type || 'application/octet-stream')
    const chunks = await this.chunkText(text, opts.chunkSize, opts.chunkOverlap)
    if (!chunks.length) {
      const fi = await vecdb.createFile(this.collectionForThread(threadId), file)
      return fi
    }
    const embeddings = await this.embedTexts(chunks)
    const dimension = embeddings[0]?.length || 0
    if (dimension <= 0) throw new Error('Embedding dimension not available')
    await this.createCollection(threadId, dimension)
    const fi = await vecdb.createFile(this.collectionForThread(threadId), file)
    await vecdb.insertChunks(
      this.collectionForThread(threadId),
      fi.id,
      chunks.map((t, i) => ({ text: t, embedding: embeddings[i] }))
    )

View on GitHub (pinned to fad3f12a14)

Solutions

  1. Call listAttachments(threadId) and skip files whose (name, path) already exist.
  2. Delete the existing attachment (deleteFile) before re-ingesting to force a refresh.
  3. Dedupe the upload list by (name, path) before calling ingestFile.
  4. Catch the error and treat it as idempotent success if re-ingest is intended.

Example fix

// before
await vecdbExt.ingestFile(threadId, file, opts)

// after
const existing = await vecdbExt.listAttachments(threadId)
const dup = existing.find((f) => f.name === file.name && f.path === file.path)
if (dup) return dup
await vecdbExt.ingestFile(threadId, file, opts)
Defensive patterns

Strategy: validation

Validate before calling

const existing = await vecdbExt.listAttachments(threadId)
const isDup = existing.some((f) => f.name === file.name && f.path === file.path)
if (isDup) {
  // skip, or deleteFile first to refresh
}

Try / catch

try {
  await vecdbExt.ingestFile(threadId, file, opts)
} catch (e) {
  if (e instanceof Error && e.message.includes('already been attached')) return // idempotent
  throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: User re-drops the same file into a thread; resume-after-error retries an already-recorded file; deduplication not performed upstream.

Related errors


AI-assisted analysis of janhq/jan@fad3f12a14 (2026-08-12). Data as JSON: /api/errors/080e142bc2c471a0. Report an issue: GitHub.