schollz/croc · error · Error

Duplicate destination path: ${normalized.path}

Error message

Duplicate destination path: ${normalized.path}

What it means

validateSenderInfo() keeps a Set of destination paths while ingesting offered files and throws when two files normalize to the same destination path. Duplicate destinations would make the second file silently overwrite the first in the download sink, so the offer is rejected.

Source

Thrown at web/src/protocol/metadata.ts:76

    throw new Error(`Invalid file size for ${file.n ?? "unnamed file"}`);
  }
  return size;
}

export function validateSenderInfo(info: SenderInfoWire): TransferOffer {
  if (info.SendingText) throw new Error("Text transfers are not supported yet");
  if (info.HashAlgorithm && info.HashAlgorithm !== "xxhash") {
    throw new Error(`Hash algorithm "${info.HashAlgorithm}" is not supported`);
  }

  const destinations = new Set<string>();
  const files: OfferedFile[] = [];
  let totalSize = 0;
  for (const wire of info.FilesToTransfer ?? []) {
    if (wire.sy) throw new Error("Symlink transfers are not supported in the browser");
    const normalized = normalizeFilePath(wire.fr ?? ".", wire.n ?? "");
    if (destinations.has(normalized.path)) {
      throw new Error(`Duplicate destination path: ${normalized.path}`);
    }
    destinations.add(normalized.path);
    const size = finiteSize(wire);
    totalSize += size;
    if (!Number.isSafeInteger(totalSize)) throw new Error("Transfer size is too large");
    files.push({
      ...normalized,
      size,
      hash: wire.h ? base64ToBytes(wire.h) : new Uint8Array(),
      modified: wire.m,
      mode: wire.md,
    });
  }

  const emptyFolders: string[] = [];
  for (const wire of info.EmptyFoldersToTransfer ?? []) {
    const folder = normalizeFolder(wire.fr ?? ".");
    if (destinations.has(folder)) {

View on GitHub (pinned to e25f1bdc04)

Solutions

  1. Fix the sender so each file has a unique (folder, basename) destination.
  2. On the receiver, refuse the duplicate offer — do not auto-rename, since the peer's intent is ambiguous.
  3. Watch for the sibling case this check cannot catch: names differing only by case are allowed here but can overwrite on case-insensitive filesystems; dedupe those yourself if targeting macOS/Windows.

Example fix

// before (offer)
[{ fr: "a/b", n: "c.txt" }, { fr: "a", n: "b/c.txt" }]
// after
[{ fr: "a/b", n: "c.txt" }, { fr: "a/b", n: "c-2.txt" }]
Defensive patterns

Strategy: validation

Validate before calling

function hasUniqueDestinations(info: SenderInfoWire): boolean {
  const seen = new Set<string>();
  for (const f of info.FilesToTransfer ?? []) {
    const key = `${f.fr ?? "."}/${f.n ?? ""}`.replaceAll("\\", "/");
    if (seen.has(key)) return false;
    seen.add(key);
  }
  return true;
}

Try / catch

try {
  const offer = validateSenderInfo(info);
} catch (error) {
  if (error instanceof Error && error.message.startsWith("Duplicate destination path")) {
    rejectOffer("ambiguous duplicate destinations");
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: An offer containing two entries whose (folder, name) pairs normalize identically — e.g. folder 'a/b' + name 'c.txt' and folder 'a' + name 'b/c.txt' (both yield 'a/b/c.txt'), or literally repeated entries. Case-sensitive comparison: 'A.txt' and 'a.txt' collide only on case-insensitive filesystems at save time, not here.

Common situations: Hostile peer probing overwrite behavior; sender with sloppy path construction producing equivalent folder/name splits; hand-built offers in tests; case-collision duplicates ('F.txt' vs 'f.txt') that pass this check but overwrite each other on macOS/Windows at write time.

Related errors


AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15). Data as JSON: /api/errors/a0ee2672d3effe22. Report an issue: GitHub.