{"record":{"id":"eac095aed4d712ed","repo":"schollz/croc","slug":"sender-did-not-provide-file-metadata","errorCode":null,"errorMessage":"Sender did not provide file metadata","messagePattern":"Sender did not provide file metadata","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/protocol/client.ts","lineNumber":733,"sourceCode":"    key = peerKeys.key;\n    data = await openDataConnections(\n      settings,\n      room,\n      dataPorts(relay.banner),\n      signal,\n    );\n    await sendControl(control, {\n      t: \"externalip\",\n      m: relay.externalIP,\n      b: peerPake.b,\n    }, key);\n    const peerIP = await receiveControl(control, key);\n    if (peerIP.t !== \"externalip\") throw new Error(\"Sender did not secure the channel\");\n\n    const fileInfo = await receiveControl(control, key);\n    if (fileInfo.t === \"error\") throw new Error(fileInfo.m || \"Sender cancelled\");\n    if (fileInfo.t !== \"fileinfo\" || !fileInfo.b) {\n      throw new Error(\"Sender did not provide file metadata\");\n    }\n    const sender = JSON.parse(textDecoder.decode(fileInfo.b)) as SenderInfoWire;\n    const offer = validateSenderInfo(sender);\n    callbacks.onStatus?.(\"Review the incoming files\");\n    const destination = await callbacks.onOffer(offer);\n    if (!destination) {\n      await sendControl(control, { t: \"error\", m: \"refusing files\" }, key);\n      throw new Error(\"Transfer refused\");\n    }\n\n    for (const folder of offer.emptyFolders) {\n      await destination.createEmptyFolder(folder);\n    }\n    let totalTransferred = 0;\n    receiver = new DataReceiver(data, key, offer.noCompress);\n    for (let fileIndex = 0; fileIndex < offer.files.length; fileIndex += 1) {\n      checkAbort(signal);\n      const file = offer.files[fileIndex];","sourceCodeStart":715,"sourceCodeEnd":751,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/client.ts#L715-L751","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isFileInfo(m: { t: string; b?: Uint8Array }): m is { t: \"fileinfo\"; b: Uint8Array } {\n  return m.t === \"fileinfo\" && !!m.b && m.b.byteLength > 0;\n}","tryCatchPattern":"catch (e) {\n  if (e instanceof Error && e.message === \"Sender did not provide file metadata\") {\n    // sender sent no/empty fileinfo: retry; if persistent, dump the raw control frame and version-check peers\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["protocol","metadata","peer-mismatch"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}