{"record":{"id":"d052e9918d1ad7fe","repo":"schollz/croc","slug":"received-a-duplicate-file-chunk","errorCode":null,"errorMessage":"Received a duplicate file chunk","messagePattern":"Received a duplicate file chunk","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/protocol/client.ts","lineNumber":616,"sourceCode":"      } catch (error) {\n        if (this.stopped) return;\n        this.stopped = true;\n        this.fail(error instanceof Error ? error : new Error(String(error)));\n      }\n    }\n  }\n\n  private fail(error: Error) {\n    this.failure ??= error;\n    this.active?.reject(error);\n    this.active = undefined;\n  }\n\n  private accept(position: number, bytes: Uint8Array) {\n    const active = this.active;\n    if (!active) throw new Error(\"Received file data before it was requested\");\n    active.queue = active.queue.then(async () => {\n      if (active.received.has(position)) throw new Error(\"Received a duplicate file chunk\");\n      if (\n        position < 0 ||\n        position % CHUNK_SIZE !== 0 ||\n        bytes.byteLength === 0 ||\n        bytes.byteLength > CHUNK_SIZE ||\n        position + bytes.byteLength > active.file.size\n      ) {\n        throw new Error(\"Received a file chunk outside the advertised file size\");\n      }\n      active.received.add(position);\n      await active.sink.writeAt(position, bytes);\n      active.bytes += bytes.byteLength;\n      active.progress(active.bytes);\n      if (active.bytes === active.file.size) {\n        this.active = undefined;\n        active.resolve();\n      } else if (active.bytes > active.file.size) {\n        throw new Error(\"Received more data than the advertised file size\");","sourceCodeStart":598,"sourceCodeEnd":634,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/client.ts#L598-L634","documentation":"Thrown inside DataReceiver's per-chunk queue when the same chunk position is delivered twice for the active file. Each position is recorded in active.received on first acceptance, so a repeat means the sender retransmitted a chunk or a data socket duplicated a frame. Because chunk ranges must tile the file exactly once, duplicates abort the transfer rather than being silently ignored.","triggerScenarios":"Sender retries a failed socket.send and the original frame also arrives; multiple data sockets assigned overlapping chunk indices (stride scheduling bug); relay duplicates a frame; sender re-sends a range after a reconnect while the receiver kept partial state.","commonSituations":"Flaky connection where sender-side retry logic duplicates frames; reconnect/resume logic that restarts a file from offset 0 while received positions persist; more data sockets on sender than recipient (port list mismatch) shifting chunk-to-socket assignment; proxies that duplicate WebSocket frames.","solutions":["Verify both peers open the same number of data connections (relay banner ports) so chunk stride partitioning matches","If implementing resume, ensure the receiver's received-set and the sender's ranges restart coherently — do not reuse a live DataReceiver across sessions","Disable or dedupe sender-side retry logic on the data sockets (reliability belongs to the relay/TCP layer)","Capture the duplicated position value in logs to see whether it is the same index across failures (stride bug) or random (network duplication)"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"catch (e) {\n  if (e instanceof Error && e.message === \"Received a duplicate file chunk\") {\n    // duplicated frame: abort transfer; dedupe-and-continue is unsafe because chunk accounting breaks\n  }\n  throw e;\n}","preventionTips":["Keep the relay banner port list identical on both peers so chunk-to-socket stride partitioning matches","Do not add sender-side retries on data sockets — the underlying transport is already reliable","If implementing resume, reset both the sender's ranges and the receiver's state together; never resume one side alone"],"tags":["data-integrity","receiver","duplicate","race-condition"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}