schollz/croc · warning · Error
Transfer refused
Error message
Transfer refused
What it means
Thrown by the recipient after callbacks.onOffer returned no destination, meaning the receiving application/user declined the offered files. The recipient sends an 'error' control message ('refusing files') to inform the sender and then throws to unwind the transfer. This is an intentional, user-driven abort rather than a fault, but it propagates as an Error to the receiveFiles caller.
Source
Thrown at web/src/protocol/client.ts:741
t: "externalip",
m: relay.externalIP,
b: peerPake.b,
}, key);
const peerIP = await receiveControl(control, key);
if (peerIP.t !== "externalip") throw new Error("Sender did not secure the channel");
const fileInfo = await receiveControl(control, key);
if (fileInfo.t === "error") throw new Error(fileInfo.m || "Sender cancelled");
if (fileInfo.t !== "fileinfo" || !fileInfo.b) {
throw new Error("Sender did not provide file metadata");
}
const sender = JSON.parse(textDecoder.decode(fileInfo.b)) as SenderInfoWire;
const offer = validateSenderInfo(sender);
callbacks.onStatus?.("Review the incoming files");
const destination = await callbacks.onOffer(offer);
if (!destination) {
await sendControl(control, { t: "error", m: "refusing files" }, key);
throw new Error("Transfer refused");
}
for (const folder of offer.emptyFolders) {
await destination.createEmptyFolder(folder);
}
let totalTransferred = 0;
receiver = new DataReceiver(data, key, offer.noCompress);
for (let fileIndex = 0; fileIndex < offer.files.length; fileIndex += 1) {
checkAbort(signal);
const file = offer.files[fileIndex];
if (file.size === 0) {
const sink = await destination.openFile(file);
try {
await sink.finalize();
callbacks.onStatus?.(`Verifying ${file.path}`);
await verifySink(sink, file.hash);
await sink.commit();
callbacks.onFileComplete?.(file.path);View on GitHub (pinned to e25f1bdc04)
Solutions
- Treat this error as expected control flow: catch it and show a 'declined' state instead of a generic failure
- If declining was unintentional, fix the onOffer implementation so it resolves only on explicit refusal and returns a destination otherwise
- Ensure the storage-picker flow distinguishes 'user cancelled the picker' from 'user declined the transfer' if you want different UX
- No protocol fix is needed — the sender is correctly notified with 'refusing files'
Example fix
// before
const destination = await callbacks.onOffer(offer);
if (!destination) {
await sendControl(control, { t: "error", m: "refusing files" }, key);
throw new Error("Transfer refused");
}
// caller: distinguish refusal from real failures
try {
await receiveFiles({ ... });
} catch (e) {
if (e instanceof Error && e.message === "Transfer refused") {
showStatus("You declined the files");
return;
}
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Make refusal explicit in your onOffer implementation
const destination = await showOfferDialog(offer); // resolves null only on explicit Decline
if (!destination) { /* let receiveFiles throw Transfer refused and handle it */ } Type guard
function isTransferRefused(e: unknown): boolean {
return e instanceof Error && e.message === "Transfer refused";
} Try / catch
try {
await receiveFiles(opts);
} catch (e) {
if (e instanceof Error && e.message === "Transfer refused") {
showStatus("You declined the files"); return; // expected, not an error state
}
throw e;
} Prevention
- Design onOffer so it resolves a destination on accept and rejects/aborts on infrastructure failures — never silently return undefined
- Map this exact message to a friendly 'declined' UI state instead of a generic error screen
- Distinguish picker-dismissal from explicit decline in your UI if the distinction matters
When it happens
Trigger: onOffer resolves to undefined/null/void because the user clicked Decline, no storage picker was granted, or application logic rejected the offer (unexpected sender, wrong file count, policy checks).
Common situations: User declines the transfer prompt; File System Access API picker dismissed without a selection; application-level policy (allow-list of peer machine IDs) refuses the sender; automated tests resolving onOffer with undefined.
Related errors
- Recipient cancelled
- Sender cancelled
- Relay returned an invalid port list: ${banner}
- Peer sent an unexpected control handshake
- Recipient did not start a croc PAKE handshake
AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15).
Data as JSON: /api/errors/ebeaacf15546fa14.
Report an issue: GitHub.