schollz/croc · critical · Error

The sender advertised xxhash

Error message

The sender advertised xxhash ${hex(expected)}, but the received file hashes to ${hex(actual)}

What it means

Thrown by verifySink when the receiving sink's xxhash digest (sink.hash() default) does not equal the hash the sender advertised in the offer. This is an end-to-end integrity failure: the bytes written to the destination do not match what the sender claims to have sent. Corrupted or truncated data must not be committed.

Solutions

  1. Abort the transfer and discard the partial destination file (sink.abort() / delete it), then retry the receive from scratch
  2. Verify you are verifying against the correct offered file's hash, not a stale or reordered offer entry
  3. If reproducible with a known-good sender, file a bug with both hex digests from the message — it indicates a write-path or chunk-offset defect
Defensive patterns

Strategy: retry

Try / catch

try { await verifySink(sink, offered.hash); } catch (e) { await sink.abort().catch(() => {}); if (e instanceof Error && e.message.includes("xxhash")) { discardPartialFile(); return retryReceive(); } throw e; }

Prevention

When it happens

Trigger: verifySink(sink, offered.hash) after sink.finalize() where the recomputed xxhash of the received file differs — caused by chunk loss/reordering, a bug in the write path, or a mismatched/malicious sender advertisement.

Common situations: Network-level corruption in relayed transfers, a destination implementation that writes chunks at wrong offsets, or comparing against a hash of a different file version. Rare in practice because croc's transport is encrypted/acked, so hitting it usually indicates a client bug or disk-level corruption.

Related errors


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

Appendix: source

Thrown at web/src/protocol/storage.ts:389

  if (supportsStreamingDownloadDestination()) {
    return new StreamingDownloadDestination();
  }
  const largest = offer.files.reduce(
    (maximum, file) => Math.max(maximum, file.size),
    0,
  );
  if (largest > 256 * 1024 * 1024) {
    throw new Error(
      "This browser cannot stream a file this large. Receive it with the croc CLI instead.",
    );
  }
  return new DownloadDestination();
}

export async function verifySink(sink: ReceiveSink, expected: Uint8Array) {
  const actual = await sink.hash();
  if (!bytesEqual(actual, expected)) {
    throw new Error(
      `The sender advertised xxhash ${hex(expected)}, but the received file hashes to ${hex(actual)}`,
    );
  }
}

export async function verifySinkSHA256(
  sink: ReceiveSink,
  expected: Uint8Array,
) {
  const actual = await sink.hash("sha256");
  if (!bytesEqual(actual, expected)) {
    throw new Error(
      `The stored file failed SHA-256 verification (${hex(actual)} != ${hex(expected)})`,
    );
  }
}

View on GitHub (pinned to e25f1bdc04)