schollz/croc · error · Error
Recipient requested an unknown file
Error message
Recipient requested an unknown file
What it means
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.
Source
Thrown at web/src/protocol/client.ts:488
for (;;) {
checkAbort(signal);
const message = await receiveControl(control, key);
if (message.t === "error") throw new Error(message.m || "Recipient refused transfer");
if (message.t === "finished") {
await sendControl(control, { t: "finished" }, key);
callbacks.onStatus?.("Transfer complete");
return;
}
if (message.t !== "recipientready" || !message.b) {
throw new Error(`Unexpected peer message: ${message.t}`);
}
const request = JSON.parse(
textDecoder.decode(message.b),
) as RemoteFileRequestWire;
const fileIndex = request.FilesToTransferCurrentNum;
const prepared = files[fileIndex];
if (!prepared) throw new Error("Recipient requested an unknown file");
callbacks.onStatus?.(`Sending ${prepared.name}`);
const beforeFile = totalTransferred;
await sendFileData(
prepared,
request.CurrentFileChunkRanges,
data,
key,
(fileBytes) => {
totalTransferred = beforeFile + fileBytes;
callbacks.onProgress?.({
fileIndex,
fileCount: files.length,
fileName: prepared.name,
fileBytes,
fileSize: prepared.size,
totalBytes: totalTransferred,
totalSize,
});View on GitHub (pinned to e25f1bdc04)
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)
Example fix
// before
const fileIndex = request.FilesToTransferCurrentNum;
const prepared = files[fileIndex];
if (!prepared) throw new Error("Recipient requested an unknown file");
// after (include the offending index and list size for diagnosis)
const fileIndex = request.FilesToTransferCurrentNum;
const prepared = files[fileIndex];
if (!prepared || !Number.isSafeInteger(fileIndex)) {
throw new Error(`Recipient requested an unknown file (index ${fileIndex}, ${files.length} prepared)`);
} Defensive patterns
Strategy: validation
Validate before calling
// Before the sender loop, assert the recipient request is in range
// (defensive wrapper around receiveControl-driven requests)
function assertKnownFile(request: RemoteFileRequestWire, files: PreparedFile[]) {
const n = request.FilesToTransferCurrentNum;
if (!Number.isSafeInteger(n) || n < 0 || n >= files.length) {
throw new Error(`Recipient requested an unknown file (index ${n}, ${files.length} prepared)`);
}
} Type guard
function isValidFileRequest(
request: unknown,
files: PreparedFile[],
): request is RemoteFileRequestWire & { FilesToTransferCurrentNum: number } {
if (typeof request !== "object" || request === null) return false;
const n = (request as RemoteFileRequestWire).FilesToTransferCurrentNum;
return Number.isSafeInteger(n) && n >= 0 && n < files.length;
} Try / catch
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; } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Recipient cancelled
- Expected recipient to close the file, got ${closed.t}
- Sender did not provide file metadata
- Expected sender to close the file, got ${close.t}
- Relay returned an invalid port list: ${banner}
AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15).
Data as JSON: /api/errors/5a94eaa7bb08aa70.
Report an issue: GitHub.