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

  1. 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
  2. Reproduce with the recipient's console open — the same error is thrown there first and reported over the wire
  3. Check recipient storage availability (File System Access permission, disk space, private-mode restrictions)
  4. 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

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


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