can1357/oh-my-pi · error

import_all: duplicate id ${id} in the imported batch. Dedupl

Error message

import_all: duplicate id ${id} in the imported batch. Deduplicate the input before calling.

What it means

`importAll()` in annotations.ts rejects an imported batch that contains the same annotation id more than once. The bulk import API requires the caller to pre-deduplicate; duplicate ids would make the upsert semantics of the batch ambiguous, so it fails fast with this error instead of silently overwriting.

Source

Thrown at packages/mnemopi/src/core/annotations.ts:339

		using statement = this.db.prepare(
			"SELECT id, memory_id, kind, value, source, confidence, created_at FROM annotations ORDER BY id",
		);
		const rows = statement.all() as AnnotationRow[];
		return rows.map(normalizeRow);
	}
	importAll(annotations: readonly AnnotationInput[], force = false): AnnotationImportStats {
		const stats: AnnotationImportStats = {
			inserted: 0,
			skipped: 0,
			overwritten: 0,
			imported_renumbered: 0,
		};
		const seenIds = new Set<number>();
		for (const item of annotations) {
			const id = rowId(item.id);
			if (id === null) continue;
			if (seenIds.has(id)) {
				throw new Error(
					`import_all: duplicate id ${id} in the imported batch. Deduplicate the input before calling.`,
				);
			}
			seenIds.add(id);
		}

		transaction(this.db, () => {
			using existingStatement = this.db.prepare(
				"SELECT id, memory_id, kind, value, source, confidence, created_at FROM annotations",
			);
			const existingRows = existingStatement.all() as AnnotationRow[];
			const existing = new Map<number, StoredAnnotationContent>();
			for (const row of existingRows) existing.set(Number(row.id), normalizeRow(row));

			using insertWithId = this.db.prepare(
				"INSERT INTO annotations (id, memory_id, kind, value, source, confidence, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
			) as WritableStatement;
			using insertWithoutId = this.db.prepare(

View on GitHub (pinned to 9690622007)

Solutions

  1. Deduplicate the input array by id before calling `importAll` (keep the first or the newest item).
  2. Strip or regenerate ids for items you intend to create fresh so the batch has unique or absent ids.
  3. If merging exports, drop duplicate ids from the older export.
  4. Wrap the call in try-catch and on this error, dedupe and retry once.

Example fix

// before
await annotations.importAll([...exportA, ...exportB]);
// after
const seen = new Set<number>();
const deduped = [...exportB, ...exportA].filter(it => {
  const id = it.id;
  if (id == null) return true;
  if (seen.has(id)) return false;
  seen.add(id);
  return true;
});
await annotations.importAll(deduped);
Defensive patterns

Strategy: validation

Validate before calling

const ids = annotations.map(a => a.id).filter(id => id != null);
if (new Set(ids).size !== ids.length) {
  throw new Error("Batch contains duplicate ids; dedupe before importAll");
}

Type guard

null

Try / catch

try {
  await annotations.importAll(batch);
} catch (err) {
  if (err instanceof Error && err.message.includes("duplicate id")) {
    const seen = new Set();
    batch = batch.filter(a => a.id == null || !seen.has(a.id) && seen.add(a.id));
    await annotations.importAll(batch);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling `annotations.importAll(items)` where two or more items in `items` carry the same `id` (after `rowId()` normalization). Items with null/unset ids are skipped and do not trigger this.

Common situations: Merging exported annotation files that overlap, re-importing a batch that already includes previously generated ids, concatenating two exports without dedupe, a producer that assigns ids non-uniquely.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/33acce66c0274f0e. Report an issue: GitHub.