{"record":{"id":"44715be52808845e","repo":"schollz/croc","slug":"symlink-transfers-are-not-supported-in-the-browser","errorCode":null,"errorMessage":"Symlink transfers are not supported in the browser","messagePattern":"Symlink transfers are not supported in the browser","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"web/src/protocol/metadata.ts","lineNumber":73,"sourceCode":"function finiteSize(file: WireFileInfo) {\n  const size = file.s ?? 0;\n  if (!Number.isSafeInteger(size) || size < 0) {\n    throw new Error(`Invalid file size for ${file.n ?? \"unnamed file\"}`);\n  }\n  return size;\n}\n\nexport function validateSenderInfo(info: SenderInfoWire): TransferOffer {\n  if (info.SendingText) throw new Error(\"Text transfers are not supported yet\");\n  if (info.HashAlgorithm && info.HashAlgorithm !== \"xxhash\") {\n    throw new Error(`Hash algorithm \"${info.HashAlgorithm}\" is not supported`);\n  }\n\n  const destinations = new Set<string>();\n  const files: OfferedFile[] = [];\n  let totalSize = 0;\n  for (const wire of info.FilesToTransfer ?? []) {\n    if (wire.sy) throw new Error(\"Symlink transfers are not supported in the browser\");\n    const normalized = normalizeFilePath(wire.fr ?? \".\", wire.n ?? \"\");\n    if (destinations.has(normalized.path)) {\n      throw new Error(`Duplicate destination path: ${normalized.path}`);\n    }\n    destinations.add(normalized.path);\n    const size = finiteSize(wire);\n    totalSize += size;\n    if (!Number.isSafeInteger(totalSize)) throw new Error(\"Transfer size is too large\");\n    files.push({\n      ...normalized,\n      size,\n      hash: wire.h ? base64ToBytes(wire.h) : new Uint8Array(),\n      modified: wire.m,\n      mode: wire.md,\n    });\n  }\n\n  const emptyFolders: string[] = [];","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/metadata.ts#L55-L91","documentation":"validateSenderInfo() throws for any offered file whose wire 'sy' flag is set. Symlinks have no portable representation in browser storage (no symlink creation in the File System Access / download pipeline), and materializing a symlink as data would leak its target or create confusion, so such offers are rejected.","triggerScenarios":"A sender includes a symlink in the transferred set (croc sends sy=true for symlinks); validateSenderInfo hits wire.sy truthy on any FilesToTransfer entry.","commonSituations":"Sending a directory that contains symlinks (common in dotfiles repos, node_modules, /usr/local trees) from a Unix sender to the web receiver; senders unaware the browser cannot receive links; scripted backups that preserve links.","solutions":["On the sender, exclude symlinks from the selection (croc's own filtering or tar the tree first without --dereference if links matter).","Archive the tree (tar/zip) on the sender and transfer the archive, preserving links inside the archive.","As the receiver, catch this and prompt the peer to re-send without symlinked entries."],"exampleFix":"# before (sender)\ncroc send ~/project          # contains symlinks\n# after (sender)\ntar czf project.tgz ~/project && croc send project.tgz","handlingStrategy":"validation","validationCode":"function isSymlinkFreeOffer(info: SenderInfoWire): boolean {\n  return (info.FilesToTransfer ?? []).every((f) => !f.sy);\n}\nif (!isSymlinkFreeOffer(info)) {\n  notifyUser(\"offer contains symlinks — sender must exclude them or archive first\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  const offer = validateSenderInfo(info);\n} catch (error) {\n  if (error instanceof Error && error.message === \"Symlink transfers are not supported in the browser\") {\n    showNotice(\"The sender included symlinks. Ask them to exclude symlinks or send a tarball.\");\n    return;\n  }\n  throw error;\n}","preventionTips":["Exclude symlinks from sender selections when the receiver is the web client.","Prefer transferring a tar/zip archive for trees that must preserve links.","Surface a clear 'unsupported in browser' message instead of retrying the same offer."],"tags":["unsupported-feature","symlink","browser-limitation","metadata"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}