chatboxai/chatbox · error · Error

Manifest contains a duplicate path

Error message

Manifest contains a duplicate path: ${descriptor.path}

What it means

Thrown by validateManifestEntries when two manifest descriptors (settings, copilots, sessionSettings, a session, or a resource) share the same archive path. Each entry must occupy a unique path so staging and checksum verification are unambiguous. Propagates out of importBackupArchive after rollback.

Solutions

  1. Regenerate the backup so the exporter assigns unique paths (safeSessionPathComponent already dedupes via sha256; a real collision points to duplicate source ids or an encoder bug).
  2. If the manifest was hand-edited, restore the original or fix the duplicate path.
  3. For two colliding session ids, rename one in the source data before re-export.

Example fix

// before: importing a manifest with two descriptors at the same path
await importBackupArchive(file, options)
// after: detect duplicate descriptor paths before importing
const paths = descriptors.map((d) => d.path)
if (new Set(paths).size !== paths.length) {
  throw new Error('Manifest has duplicate paths: ' + paths.filter((p, i) => paths.indexOf(p) !== i))
}
Defensive patterns

Strategy: validation

Validate before calling

// Before import, fail fast on duplicate descriptor paths in the manifest.
const paths = [
  manifest.data.settings?.path,
  manifest.data.copilots?.path,
  manifest.data.sessionSettings?.path,
  ...manifest.sessions.map((s) => s.path),
  ...manifest.resources.map((r) => r.path),
].filter(Boolean) as string[]
if (new Set(paths).size !== paths.length) {
  throw new Error('Manifest declares duplicate paths')
}

Try / catch

try {
  await importBackupArchive(file, options)
} catch (error) {
  if (error instanceof Error && error.message.startsWith('Manifest contains a duplicate path')) {
    // Regenerate the backup; do not attempt to merge paths by hand.
    throw new Error('Backup manifest is inconsistent; re-create it', { cause: error })
  }
  throw error
}

Prevention

When it happens

Trigger: A manifest where, for example, two sessions derive the same sessions/<id>/session.json path (collision after safeSessionPathComponent sha256 fallback), or a resource path collides with another descriptor's path.

Common situations: Two sessions whose ids hash to the same sha256 prefix producing duplicate session.json paths; a hand-crafted or tampered manifest; a bug in path generation during export.

Related errors


AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12). Data as JSON: /api/errors/ea2816beb2ddcbf3. Report an issue: GitHub.

Appendix: source

Thrown at src/renderer/packages/backup/import-backup.ts:90

  }
}

function parseJson(bytes: Uint8Array, path: string): unknown {
  if (bytes.length > MAX_BACKUP_JSON_ENTRY_BYTES) throw new Error(`Backup JSON entry is too large: ${path}`)
  return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes)) as unknown
}

function validateManifestEntries(manifest: BackupManifest, stagedEntries: Map<string, StagedEntry>) {
  const descriptors = [
    manifest.data.settings,
    manifest.data.copilots,
    manifest.data.sessionSettings,
    ...manifest.sessions,
    ...manifest.resources,
  ].filter((entry): entry is NonNullable<typeof entry> => Boolean(entry))
  const expectedPaths = new Set<string>([BACKUP_MANIFEST_PATH])
  for (const descriptor of descriptors) {
    if (expectedPaths.has(descriptor.path)) throw new Error(`Manifest contains a duplicate path: ${descriptor.path}`)
    expectedPaths.add(descriptor.path)
    const staged = stagedEntries.get(descriptor.path)
    if (!staged) throw new Error(`Backup entry is missing: ${descriptor.path}`)
    if (staged.size !== descriptor.size) throw new Error(`Backup entry size mismatch: ${descriptor.path}`)
    if (staged.checksum.value !== descriptor.checksum.value) {
      throw new Error(`Backup entry checksum mismatch: ${descriptor.path}`)
    }
  }
  for (const path of stagedEntries.keys()) {
    if (!expectedPaths.has(path)) throw new Error(`Backup contains an entry not listed in manifest: ${path}`)
  }
  if (expectedPaths.size !== stagedEntries.size) throw new Error('Backup manifest entry list is incomplete')
  validateBackupManifestGraph(manifest)
}

async function readStagedResource(storage: BackupStorage, plan: Pick<ResourceWritePlan, 'resource' | 'tempKey'>) {
  const base64 = await storage.getBlob(plan.tempKey)
  if (base64 === null) throw new Error(`Staged resource is missing: ${plan.resource.path}`)

View on GitHub (pinned to 81571269ad)