{"record":{"id":"11fbc13b4e58e5f0","repo":"schollz/croc","slug":"received-file-data-before-it-was-requested","errorCode":null,"errorMessage":"Received file data before it was requested","messagePattern":"Received file data before it was requested","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/protocol/client.ts","lineNumber":614,"sourceCode":"        const bytes = payload.slice(8);\n        await this.accept(position, bytes);\n      } 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();","sourceCodeStart":596,"sourceCodeEnd":632,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/client.ts#L596-L632","documentation":"Thrown by DataReceiver.accept when a data chunk arrives while no file receive is active (this.active is undefined). The protocol only streams data after the recipient sends 'recipientready' for a specific file, so unsolicited data means the sender started streaming early, continued after file completion, or sent data for a file the recipient never requested. It fails the receiver loop and aborts the transfer.","triggerScenarios":"Sender begins streaming before receiving recipientready; sender sends extra chunks after active.bytes === file.size resolved the promise (this.active reset to undefined); leftover in-flight frames from a previous file arriving after completion; sender and recipient disagree on file count so data for file N+1 arrives before its request.","commonSituations":"Sender implementation that pipelines the next file without waiting for close-sender/close-recipient; race where the final chunks of a completed file are still queued on a data socket while the recipient starts the next file (the dup-check then fires, but unsolicited-data fires when active is undefined between files); reconnect scenarios where the sender resumes sending immediately.","solutions":["Check sender loop ordering: data must only follow a recipientready and stop before close-sender","When resuming/reconnecting, drain or re-create data sockets so stale frames from the previous session cannot arrive","Ensure the sender's chunk scheduling across multiple sockets completes before it sends its close message","Add logging of active-file lifecycle (set at receive(), cleared at completion) to identify which boundary leaks the unsolicited chunk"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"catch (e) {\n  if (e instanceof Error && e.message === \"Received file data before it was requested\") {\n    // sender streamed outside the protocol window: abort and restart, resuming is unsafe\n  }\n  throw e;\n}","preventionTips":["If you implement a sender, only stream data between recipientready and close-sender","Drain or recreate data sockets on reconnect so stale frames never reach a fresh DataReceiver","Do not reuse a DataReceiver instance across transfers; construct it per receiveFiles call as the library does"],"tags":["protocol","state-machine","receiver","race-condition"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}