schollz/croc · error · Error
Sender did not provide file metadata
Error message
Sender did not provide file metadata
What it means
Thrown by the recipient when the message following externalip is neither 'error' nor a 'fileinfo' carrying a b payload. fileinfo carries the JSON-encoded SenderInfoWire (file list, hashes, sizes, empty folders) that the recipient validates via validateSenderInfo and shows through onOffer. A missing or malformed fileinfo means the sender never delivered usable transfer metadata, so there is nothing to accept.
Source
Thrown at web/src/protocol/client.ts:733
key = peerKeys.key;
data = await openDataConnections(
settings,
room,
dataPorts(relay.banner),
signal,
);
await sendControl(control, {
t: "externalip",
m: relay.externalIP,
b: peerPake.b,
}, key);
const peerIP = await receiveControl(control, key);
if (peerIP.t !== "externalip") throw new Error("Sender did not secure the channel");
const fileInfo = await receiveControl(control, key);
if (fileInfo.t === "error") throw new Error(fileInfo.m || "Sender cancelled");
if (fileInfo.t !== "fileinfo" || !fileInfo.b) {
throw new Error("Sender did not provide file metadata");
}
const sender = JSON.parse(textDecoder.decode(fileInfo.b)) as SenderInfoWire;
const offer = validateSenderInfo(sender);
callbacks.onStatus?.("Review the incoming files");
const destination = await callbacks.onOffer(offer);
if (!destination) {
await sendControl(control, { t: "error", m: "refusing files" }, key);
throw new Error("Transfer refused");
}
for (const folder of offer.emptyFolders) {
await destination.createEmptyFolder(folder);
}
let totalTransferred = 0;
receiver = new DataReceiver(data, key, offer.noCompress);
for (let fileIndex = 0; fileIndex < offer.files.length; fileIndex += 1) {
checkAbort(signal);
const file = offer.files[fileIndex];View on GitHub (pinned to e25f1bdc04)
Solutions
- Log the received message type and b length at the sender and recipient to see whether fileinfo was sent, dropped, or emptied
- Verify senderInfo() serialization runs without throwing and produces non-empty JSON (check for circular refs or enormous lists)
- Upgrade both peers to the same protocol version
- Retry on a stable network; dropped control frames mid-handshake usually indicate relay issues
Defensive patterns
Strategy: type-guard
Type guard
function isFileInfo(m: { t: string; b?: Uint8Array }): m is { t: "fileinfo"; b: Uint8Array } {
return m.t === "fileinfo" && !!m.b && m.b.byteLength > 0;
} Try / catch
catch (e) {
if (e instanceof Error && e.message === "Sender did not provide file metadata") {
// sender sent no/empty fileinfo: retry; if persistent, dump the raw control frame and version-check peers
}
throw e;
} Prevention
- When implementing a sender, assert JSON.stringify(senderInfo(files)).length > 0 before sending fileinfo
- Version-lock both peers so the post-handshake message sequence matches
- Add frame-level logging (t + byteLength) around handshake steps when debugging protocol issues
When it happens
Trigger: Sender sends fileinfo with an empty b payload (serialization bug); sender disconnects and the relay delivers a close/other frame; a version-skewed sender sends a different message type at this point; memory/stringify failure on the sender producing empty bytes.
Common situations: Mock senders that omit the fileinfo payload; sender crashes during JSON.stringify of a huge file list; protocol drift after adding new message types; relay replacing the frame with an error notice.
Related errors
- Recipient requested an unknown file
- Expected recipient to close the file, got ${closed.t}
- Expected sender to close the file, got ${close.t}
- Remote filename must be a basename: ${nameValue}
- Text transfers are not supported yet
AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15).
Data as JSON: /api/errors/eac095aed4d712ed.
Report an issue: GitHub.