{"record":{"id":"7f87aa36ee55554f","repo":"chatboxai/chatbox","slug":"duplicate-zip-entry-entry-path","errorCode":null,"errorMessage":"Duplicate ZIP entry: ${entry.path}","messagePattern":"Duplicate ZIP entry: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/renderer/packages/backup/zip.ts","lineNumber":111,"sourceCode":"      return\n    }\n    if (data.length > 0) {\n      queue.push(data)\n      queuedBytes += data.length\n    }\n    if (final) outputComplete = true\n    notifyConsumer()\n  })\n\n  const onAbort = () => zip.terminate()\n  signal?.addEventListener('abort', onAbort, { once: true })\n\n  void (async () => {\n    try {\n      for await (const entry of entries) {\n        throwIfAborted(signal)\n        assertSafeArchivePath(entry.path)\n        if (seenPaths.has(entry.path)) throw new Error(`Duplicate ZIP entry: ${entry.path}`)\n        seenPaths.add(entry.path)\n        const file =\n          entry.compress === false ? new ZipPassThrough(entry.path) : new ZipDeflate(entry.path, { level: 6 })\n        zip.add(file)\n        let pushed = false\n        for await (const chunk of toChunks(entry.data)) {\n          throwIfAborted(signal)\n          if (chunk.length === 0) continue\n          if (pushed) {\n            file.push(chunk)\n          } else {\n            file.push(chunk)\n            pushed = true\n          }\n          await waitForDrain()\n        }\n        file.push(EMPTY_BYTES, true)\n        await waitForDrain()","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/renderer/packages/backup/zip.ts#L93-L129","documentation":"Thrown by createZipStream's producer when an incoming entry's path has already been seen in the same archive. A ZIP central directory can technically allow duplicate names but extraction/restore semantics break, so the stream rejects duplicates up front.","triggerScenarios":"Two entries passed to createZipStream share the same path string.","commonSituations":"Export pipeline emits the same file path twice (e.g. a resource and a session both written to 'data/x.json'); case-only duplicates on case-insensitive systems; a merge without dedupe.","solutions":["Dedupe entries by path before streaming them into createZipStream.","Namespace paths per entry type (e.g. 'sessions/<id>.json', 'resources/<id>').","Audit the producer that builds the entry list."],"exampleFix":"// before\nyield* allEntries // may contain duplicate paths\n\n// after\nconst seen = new Set<string>()\nfor (const e of allEntries) {\n  if (seen.has(e.path)) continue\n  seen.add(e.path)\n  yield e\n}","handlingStrategy":"validation","validationCode":"function findDuplicateEntryPaths(entries: Iterable<ZipArchiveEntry>): string[] {\n  const seen = new Set<string>()\n  const dups = new Set<string>()\n  for (const e of entries) (seen.has(e.path) ? dups : seen).add(e.path)\n  return [...dups]\n}","typeGuard":null,"tryCatchPattern":"try {\n  for await (const chunk of createZipStream(entries, signal)) writeChunk(chunk)\n} catch (error) {\n  if (/Duplicate ZIP entry/.test((error as Error).message)) reportDuplicatePaths(error)\n  throw error\n}","preventionTips":["Namespace entry paths by type (sessions/<id>, resources/<id>) so collisions cannot occur.","Dedupe entries by path before streaming.","Treat path case-sensitively even on case-insensitive systems."],"tags":["zip","validation","dedupe"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}