{"record":{"id":"5a94eaa7bb08aa70","repo":"schollz/croc","slug":"recipient-requested-an-unknown-file","errorCode":null,"errorMessage":"Recipient requested an unknown file","messagePattern":"Recipient requested an unknown file","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/protocol/client.ts","lineNumber":488,"sourceCode":"    for (;;) {\n      checkAbort(signal);\n      const message = await receiveControl(control, key);\n      if (message.t === \"error\") throw new Error(message.m || \"Recipient refused transfer\");\n      if (message.t === \"finished\") {\n        await sendControl(control, { t: \"finished\" }, key);\n        callbacks.onStatus?.(\"Transfer complete\");\n        return;\n      }\n      if (message.t !== \"recipientready\" || !message.b) {\n        throw new Error(`Unexpected peer message: ${message.t}`);\n      }\n\n      const request = JSON.parse(\n        textDecoder.decode(message.b),\n      ) as RemoteFileRequestWire;\n      const fileIndex = request.FilesToTransferCurrentNum;\n      const prepared = files[fileIndex];\n      if (!prepared) throw new Error(\"Recipient requested an unknown file\");\n      callbacks.onStatus?.(`Sending ${prepared.name}`);\n      const beforeFile = totalTransferred;\n      await sendFileData(\n        prepared,\n        request.CurrentFileChunkRanges,\n        data,\n        key,\n        (fileBytes) => {\n          totalTransferred = beforeFile + fileBytes;\n          callbacks.onProgress?.({\n            fileIndex,\n            fileCount: files.length,\n            fileName: prepared.name,\n            fileBytes,\n            fileSize: prepared.size,\n            totalBytes: totalTransferred,\n            totalSize,\n          });","sourceCodeStart":470,"sourceCodeEnd":506,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/client.ts#L470-L506","documentation":"Thrown by the sender (sendFiles) when a 'recipientready' control message carries a FilesToTransferCurrentNum index that has no matching entry in the locally prepared files array. The recipient told the sender which file to stream next, but that index does not exist on this side, so the state machines have diverged and the transfer is aborted. It almost always indicates the two peers disagree about the file list negotiated in the 'fileinfo' message.","triggerScenarios":"The recipient's offer (from validateSenderInfo over the sender's fileinfo) contains more files than the sender prepared; a resume/reconnect peer sends an index based on a different transfer; the sender's `files` array passed to sendFiles is mutated or reordered between prepareFiles and the recipientready loop; a malicious or buggy peer crafts a recipientready with an out-of-range FilesToTransferCurrentNum.","commonSituations":"Calling sendFiles twice with different prepared arrays on the same code phrase; recipient and sender using different code-phrase rooms so a third party completes the handshake; version mismatch between web client and CLI croc where file numbering differs; test harnesses that stub the recipientready payload with a hardcoded index.","solutions":["Confirm the exact same PreparedFile[] from prepareFiles is passed to sendFiles and is not mutated afterwards","Verify both peers derived their file list from the same fileinfo exchange (check senderInfo() output vs the offer shown by onOffer) and that the code phrase is unique to this transfer","Check request.FilesToTransferCurrentNum is within [0, files.length) before indexing, and treat out-of-range as a protocol error with a clear message","If interoperating with croc CLI, confirm both sides use compatible protocol versions (PAKE_PROTOCOL_VERSION negotiation happens earlier and would fail first)"],"exampleFix":"// before\nconst fileIndex = request.FilesToTransferCurrentNum;\nconst prepared = files[fileIndex];\nif (!prepared) throw new Error(\"Recipient requested an unknown file\");\n\n// after (include the offending index and list size for diagnosis)\nconst fileIndex = request.FilesToTransferCurrentNum;\nconst prepared = files[fileIndex];\nif (!prepared || !Number.isSafeInteger(fileIndex)) {\n  throw new Error(`Recipient requested an unknown file (index ${fileIndex}, ${files.length} prepared)`);\n}","handlingStrategy":"validation","validationCode":"// Before the sender loop, assert the recipient request is in range\n// (defensive wrapper around receiveControl-driven requests)\nfunction assertKnownFile(request: RemoteFileRequestWire, files: PreparedFile[]) {\n  const n = request.FilesToTransferCurrentNum;\n  if (!Number.isSafeInteger(n) || n < 0 || n >= files.length) {\n    throw new Error(`Recipient requested an unknown file (index ${n}, ${files.length} prepared)`);\n  }\n}","typeGuard":"function isValidFileRequest(\n  request: unknown,\n  files: PreparedFile[],\n): request is RemoteFileRequestWire & { FilesToTransferCurrentNum: number } {\n  if (typeof request !== \"object\" || request === null) return false;\n  const n = (request as RemoteFileRequestWire).FilesToTransferCurrentNum;\n  return Number.isSafeInteger(n) && n >= 0 && n < files.length;\n}","tryCatchPattern":"catch (e) { if (e instanceof Error && e.message.startsWith(\"Recipient requested an unknown file\")) { /* peer/protocol divergence: abort, do not retry with same state */ } throw e; }","preventionTips":["Pass the exact PreparedFile[] returned by prepareFiles to sendFiles and freeze it (Object.freeze) so it cannot be mutated mid-transfer","Use a unique code phrase per transfer so no third peer can inject recipientready messages","Never construct the files array from two different prepareFiles calls on one code phrase"],"tags":["protocol","file-transfer","peer-mismatch"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}