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.
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)
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
- Sender cancelled
- close.m || "Sender cancelled"
- Recipient requested an unknown file
- Transfer refused
- Relay returned an invalid port list: ${banner}
AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15).
Data as JSON: /api/errors/30dac2807e4590a1.
Report an issue: GitHub.