{"record":{"id":"64cc3ee5685d4ea1","repo":"schollz/croc","slug":"the-sender-advertised-xxhash-hex-expected-but","errorCode":null,"errorMessage":"The sender advertised xxhash ${hex(expected)}, but the received file hashes to ${hex(actual)}","messagePattern":"The sender advertised xxhash (.+?), but the received file hashes to (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"web/src/protocol/storage.ts","lineNumber":389,"sourceCode":"  if (supportsStreamingDownloadDestination()) {\n    return new StreamingDownloadDestination();\n  }\n  const largest = offer.files.reduce(\n    (maximum, file) => Math.max(maximum, file.size),\n    0,\n  );\n  if (largest > 256 * 1024 * 1024) {\n    throw new Error(\n      \"This browser cannot stream a file this large. Receive it with the croc CLI instead.\",\n    );\n  }\n  return new DownloadDestination();\n}\n\nexport async function verifySink(sink: ReceiveSink, expected: Uint8Array) {\n  const actual = await sink.hash();\n  if (!bytesEqual(actual, expected)) {\n    throw new Error(\n      `The sender advertised xxhash ${hex(expected)}, but the received file hashes to ${hex(actual)}`,\n    );\n  }\n}\n\nexport async function verifySinkSHA256(\n  sink: ReceiveSink,\n  expected: Uint8Array,\n) {\n  const actual = await sink.hash(\"sha256\");\n  if (!bytesEqual(actual, expected)) {\n    throw new Error(\n      `The stored file failed SHA-256 verification (${hex(actual)} != ${hex(expected)})`,\n    );\n  }\n}\n","sourceCodeStart":371,"sourceCodeEnd":406,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/storage.ts#L371-L406","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"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; }","preventionTips":["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"],"tags":["integrity","hash","verification","corruption","xxhash"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}