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
- Abort the transfer and discard the partial destination file (sink.abort() / delete it), then retry the receive from scratch
- Verify you are verifying against the correct offered file's hash, not a stale or reordered offer entry
- 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
- Always abort the sink and delete the partial destination file on hash mismatch — never commit
- Log both digests (message includes them) to distinguish corruption from systematic write-path bugs
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
- The stored file failed SHA-256 verification (${hex(actual)}
- Streaming downloads support SHA-256 verification only
- Received an invalid file chunk
- Received a file position that is too large
- Hash algorithm "${info.HashAlgorithm}" is not supported
AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15).
Data as JSON: /api/errors/64cc3ee5685d4ea1.
Report an issue: GitHub.