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
- 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.
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
- 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.
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
- File '${file.name}' has already been attached to this projec
- Vector DB extension not available
- Model ${modelId} already exists
- Model already loaded!!
- Model ${modelId} already exists
AI-assisted analysis of janhq/jan@fad3f12a14 (2026-08-12).
Data as JSON: /api/errors/080e142bc2c471a0.
Report an issue: GitHub.