schollz/croc · critical · Error

The sender advertised xxhash ${hex(expected)}, but the recei

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.

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)

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.