schollz/croc · error · Error
Recipient cancelled
Error message
Recipient cancelled
What it means
Thrown by the sender after streaming a file when the next control message from the recipient is an 'error' message (with no text, the fallback 'Recipient cancelled' is used). The recipient explicitly reported a failure over the control channel via reportPeerError, so the sender surfaces it and tears the transfer down. The recipient's own error string, when present, is preserved in the message.
Solutions
- Read the error text: a non-empty message pinpoints the recipient-side failure; 'Recipient cancelled' with no text usually means the recipient aborted via signal or closed the page
- Reproduce with the recipient's console open — the same error is thrown there first and reported over the wire
- Check recipient storage availability (File System Access permission, disk space, private-mode restrictions)
- If it happens reproducibly at the same byte offset, suspect data-channel corruption: test over a different relay/network and confirm both sides use the same transfer settings
Defensive patterns
Strategy: try-catch
Type guard
function isPeerErrorMessage(e: unknown): e is Error {
return e instanceof Error && /Recipient cancelled/.test(e.message);
} Try / catch
try {
await sendFiles(opts);
} catch (e) {
if (e instanceof Error && /Recipient cancelled/.test(e.message)) {
showStatus("The receiver stopped the transfer"); // user-visible cancellation, not a bug
return;
}
throw e; // genuine failure
} Prevention
- Treat recipient-reported errors as expected control flow and surface the forwarded message text to the sender's user
- Keep an AbortSignal wired on both peers so cancellations produce clean AbortError/peer-error paths instead of socket teardown
- Test the decline/cancel path explicitly in your UI so this error maps to a friendly state
When it happens
Trigger: The recipient aborts mid-file: user cancels, onOffer refusal, a DataReceiver read failure (invalid/duplicate/out-of-range chunk) that rejects receivePromise and triggers the recipient's catch -> reportPeerError, recipient hash verification fails (verifySink), or the recipient's storage sink throws (quota, write error).
Common situations: Recipient browser tab closed or AbortSignal fired during transfer; recipient disk/partition or File System Access handle revoked; corrupted chunk from a relay or mismatched key causing DataReceiver validation failures; recipient user clicks 'Decline' or 'Cancel' in the offer UI after data started.
Related errors
- close.m || "Sender cancelled"
- Sender cancelled
- Recipient requested an unknown file
- Transfer refused
- AbortError
AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15).
Data as JSON: /api/errors/30dac2807e4590a1.
Report an issue: GitHub.
Appendix: source
Thrown at web/src/protocol/client.ts:511
request.CurrentFileChunkRanges,
data,
key,
(fileBytes) => {
totalTransferred = beforeFile + fileBytes;
callbacks.onProgress?.({
fileIndex,
fileCount: files.length,
fileName: prepared.name,
fileBytes,
fileSize: prepared.size,
totalBytes: totalTransferred,
totalSize,
});
},
signal,
);
const closed = await receiveControl(control, key);
if (closed.t === "error") throw new Error(closed.m || "Recipient cancelled");
if (closed.t !== "close-sender") {
throw new Error(`Expected recipient to close the file, got ${closed.t}`);
}
await sendControl(control, { t: "close-recipient" }, key);
callbacks.onFileComplete?.(prepared.name);
}
} catch (error) {
await reportPeerError(control, key, error);
throw error;
} finally {
closeAll(control, data);
}
}
type ActiveReceive = {
file: OfferedFile;
sink: ReceiveSink;
received: Set<number>;View on GitHub (pinned to e25f1bdc04)