schollz/croc · warning · Error
Symlink transfers are not supported in the browser
Error message
Symlink transfers are not supported in the browser
What it means
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.
Source
Thrown at web/src/protocol/metadata.ts:73
function finiteSize(file: WireFileInfo) {
const size = file.s ?? 0;
if (!Number.isSafeInteger(size) || size < 0) {
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[] = [];View on GitHub (pinned to e25f1bdc04)
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.
Example fix
# before (sender) croc send ~/project # contains symlinks # after (sender) tar czf project.tgz ~/project && croc send project.tgz
Defensive patterns
Strategy: validation
Validate before calling
function isSymlinkFreeOffer(info: SenderInfoWire): boolean {
return (info.FilesToTransfer ?? []).every((f) => !f.sy);
}
if (!isSymlinkFreeOffer(info)) {
notifyUser("offer contains symlinks — sender must exclude them or archive first");
} Try / catch
try {
const offer = validateSenderInfo(info);
} catch (error) {
if (error instanceof Error && error.message === "Symlink transfers are not supported in the browser") {
showNotice("The sender included symlinks. Ask them to exclude symlinks or send a tarball.");
return;
}
throw error;
} Prevention
- 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.
When it happens
Trigger: A sender includes a symlink in the transferred set (croc sends sy=true for symlinks); validateSenderInfo hits wire.sy truthy on any FilesToTransfer entry.
Common situations: 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.
Related errors
- Text transfers are not supported yet
- Sender did not provide file metadata
- A remote path contains a null byte
- Remote path escapes the destination: ${value}
- Remote path contains a non-printable character: ${value}
AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15).
Data as JSON: /api/errors/44715be52808845e.
Report an issue: GitHub.