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

  1. Treat this error as expected control flow: catch it and show a 'declined' state instead of a generic failure
  2. If declining was unintentional, fix the onOffer implementation so it resolves only on explicit refusal and returns a destination otherwise
  3. Ensure the storage-picker flow distinguishes 'user cancelled the picker' from 'user declined the transfer' if you want different UX
  4. 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

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


AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15). Data as JSON: /api/errors/ebeaacf15546fa14. Report an issue: GitHub.