janhq/jan · error · Error

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

Error message

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

What it means

Thrown by VectorDBExtension.ingestFileForProject() after the collection is created and existing attachments are listed, when an attachment with the same name AND path already exists. Duplicate detection is by (name, path) pair, so a renamed copy or a same-name file from a different path is allowed. Note the collection is created before the duplicate check, so a rejected ingest still leaves the collection in place.

Source

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

    const text = await ragApi.parseDocument(file.path, file.type || 'application/octet-stream')
    const chunks = await this.chunkText(text, opts.chunkSize, opts.chunkOverlap)

    // Get embeddings to determine dimension - use a default if no chunks
    let dimension = 0
    if (chunks.length > 0) {
      const embeddings = await this.embedTexts(chunks)
      dimension = embeddings[0]?.length || 0
    }

    // Ensure collection exists (use default dimension 384 if no embeddings yet)
    const collectionDimension = dimension > 0 ? dimension : 384
    await this.createCollectionForProject(projectId, collectionDimension)

    // Now check for duplicates
    const existingFiles = await vecdb.listAttachments(this.collectionForProject(projectId)).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 project`)
    }

    if (!chunks.length) {
      const fi = await vecdb.createFile(this.collectionForProject(projectId), file)
      return fi
    }

    // Re-embed if we got dimension from createCollection
    const embeddings = await this.embedTexts(chunks)
    const finalDimension = embeddings[0]?.length || 0
    if (finalDimension <= 0) throw new Error('Embedding dimension not available')

    // Ensure collection has correct dimension
    if (finalDimension !== collectionDimension) {
      await this.deleteCollectionForProject(projectId)
      await this.createCollectionForProject(projectId, finalDimension)
    }

View on GitHub (pinned to fad3f12a14)

Solutions

  1. Check existing attachments via listAttachmentsForProject before ingesting and skip duplicates.
  2. Delete the existing attachment (deleteFileForProject) before re-ingesting if you want a fresh copy.
  3. Dedupe the file list by (name, path) before calling ingest.
  4. Treat this error as a no-op success if idempotent re-ingest is desired.

Example fix

// before
await vecdbExt.ingestFileForProject(projectId, file, opts)

// after
const existing = await vecdbExt.listAttachmentsForProject(projectId)
const dup = existing.find((f) => f.name === file.name && f.path === file.path)
if (dup) return dup // idempotent: return the existing record
await vecdbExt.ingestFileForProject(projectId, file, opts)
Defensive patterns

Strategy: validation

Validate before calling

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

Try / catch

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

Prevention

When it happens

Trigger: Calling ingestFileForProject twice with the same file; re-running ingestion after a partial failure that already created the attachment record; a retry loop that does not dedupe.

Common situations: User re-attaches the same document; automation re-ingests a project on every run; a resume-after-crash retries the already-recorded file.

Related errors


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