{"record":{"id":"8f45bd900fa898fb","repo":"schollz/croc","slug":"duplicate-filename-outgoingname","errorCode":null,"errorMessage":"Duplicate filename: ${outgoingName}","messagePattern":"Duplicate filename: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"web/src/protocol/client.ts","lineNumber":276,"sourceCode":"      m: errorMessage(error).slice(0, 500),\n    }, key);\n  } catch {\n    // The connection may already be gone.\n  }\n}\n\nexport async function prepareFiles(\n  selected: File[],\n  callbacks: TransferCallbacks = {},\n  signal?: AbortSignal,\n) {\n  if (selected.length === 0) throw new Error(\"Choose at least one file\");\n  const names = new Set<string>();\n  const outgoingNames = selected.map((file) => normalizeOutgoingFileName(file.name));\n  for (let index = 0; index < selected.length; index += 1) {\n    const file = selected[index];\n    const outgoingName = outgoingNames[index];\n    if (names.has(outgoingName)) throw new Error(`Duplicate filename: ${outgoingName}`);\n    if (!Number.isSafeInteger(file.size)) throw new Error(`File is too large: ${file.name}`);\n    names.add(outgoingName);\n  }\n\n  const prepared: PreparedFile[] = [];\n  const engine = wasm();\n  for (let index = 0; index < selected.length; index += 1) {\n    checkAbort(signal);\n    const file = selected[index];\n    callbacks.onStatus?.(`Hashing ${index + 1}/${selected.length}: ${file.name}`);\n    const hashHandle = await engine.hashInit();\n    const reader = file.stream().getReader();\n    try {\n      for (;;) {\n        checkAbort(signal);\n        const { done, value } = await reader.read();\n        if (done) break;\n        await engine.hashUpdate(hashHandle, value);","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/client.ts#L258-L294","documentation":"prepareFiles builds a Set of outgoing filenames (after normalizeOutgoingFileName) and rejects any duplicate before hashing starts. Duplicate names would collide on the recipient's filesystem, so they are refused up front. Normalization means names that differ in raw form can still collide (e.g. differing path separators mapping to the same basename).","triggerScenarios":"Selecting two files with the same basename from different folders; two files whose normalized names (path stripping, separator unification) converge; re-adding the same file twice in the picker.","commonSituations":"Users dragging a whole tree where 'img/logo.png' appears in two subfolders; UI allowing repeated additions without dedupe.","solutions":["Rename one of the colliding files before selecting it","Dedupe in the UI when files are added (skip or prompt on duplicate normalized names)","Mirror normalizeOutgoingFileName in the picker check so the preview matches what prepareFiles enforces"],"exampleFix":"// before\nconst selected = [...folderAFiles, ...folderBFiles]; // both contain 'report.pdf'\nawait prepareFiles(selected);\n\n// after: dedupe on add\nfunction addFiles(current, incoming) {\n  const seen = new Set(current.map((f) => normalizeOutgoingFileName(f.name)));\n  return [...current, ...incoming.filter((f) => {\n    const name = normalizeOutgoingFileName(f.name);\n    if (seen.has(name)) return false;\n    seen.add(name);\n    return true;\n  })];\n}","handlingStrategy":"validation","validationCode":"import { normalizeOutgoingFileName } from \"../protocol/metadata\";\nfunction hasDuplicateNames(files) {\n  const seen = new Set();\n  for (const f of files) {\n    const name = normalizeOutgoingFileName(f.name);\n    if (seen.has(name)) return name;\n    seen.add(name);\n  }\n  return null;\n}\nconst dup = hasDuplicateNames(selected);\nif (dup) throw new Error(`Duplicate filename: ${dup}`);","typeGuard":null,"tryCatchPattern":"try { await prepareFiles(selected); } catch (e) {\n  if (/^Duplicate filename:/.test(e.message)) { markDuplicateInUI(e.message); return; }\n  throw e;\n}","preventionTips":["Dedupe on add in the file picker UI using the same normalization","Warn users dragging whole trees that basenames must be unique","Re-check right before submit; selections can change between screens"],"tags":["validation","files","duplicate"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}