{"record":{"id":"bc07cea31af3dc33","repo":"schollz/croc","slug":"expected-sender-to-close-the-file-got-close-t","errorCode":null,"errorMessage":"Expected sender to close the file, got ${close.t}","messagePattern":"Expected sender to close the file, got (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/protocol/client.ts","lineNumber":799,"sourceCode":"          });\n        });\n        const request: RemoteFileRequestWire = {\n          CurrentFileChunkRanges: [],\n          FilesToTransferCurrentNum: fileIndex,\n          MachineID: machineID(),\n          ReconnectVersion: 0,\n        };\n        await sendControl(control, {\n          t: \"recipientready\",\n          b: textEncoder.encode(JSON.stringify(request)),\n        }, key);\n        await receivePromise;\n        await sink.finalize();\n        await sendControl(control, { t: \"close-sender\" }, key);\n        const close = await receiveControl(control, key);\n        if (close.t === \"error\") throw new Error(close.m || \"Sender cancelled\");\n        if (close.t !== \"close-recipient\") {\n          throw new Error(`Expected sender to close the file, got ${close.t}`);\n        }\n        callbacks.onStatus?.(`Verifying ${file.path}`);\n        await verifySink(sink, file.hash);\n        await sink.commit();\n        totalTransferred = beforeFile + file.size;\n        callbacks.onFileComplete?.(file.path);\n      } catch (error) {\n        await sink.abort();\n        throw error;\n      }\n    }\n\n    await sendControl(control, { t: \"finished\" }, key);\n    const finishedMessage = await receiveControl(control, key);\n    if (finishedMessage.t !== \"finished\") {\n      throw new Error(`Expected transfer completion, got ${finishedMessage.t}`);\n    }\n    callbacks.onStatus?.(\"Transfer complete\");","sourceCodeStart":781,"sourceCodeEnd":817,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/client.ts#L781-L817","documentation":"Thrown by the recipient when the sender's reply to 'close-sender' is neither 'error' nor 'close-recipient'. The per-file closing choreography requires the sender to acknowledge with close-recipient; any other message type means the sender's protocol state machine diverged (e.g. it sent the next recipientready itself, a finished message, or stale/duplicated frames arrived), so the recipient aborts before verifying and committing the file.","triggerScenarios":"Sender skips the close-recipient acknowledgement; duplicated or reordered control frames from the relay deliver an old message here; a non-conforming sender implementation replies with a different message; sender crash followed by relay garbage on the control socket.","commonSituations":"Custom/mock senders that end the file loop without close-recipient; relay duplication bugs; protocol drift between client versions; concurrent transfers colliding on a reused code phrase/room.","solutions":["Log the unexpected type (embedded in the message) and compare with the expected choreography: close-sender -> close-recipient","Use unique code phrases per transfer to prevent room collisions","Ensure sender implementations always reply close-recipient after each file's close-sender","Upgrade both peers so the closing sequence matches"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isUnexpectedSenderClose(e: unknown): boolean {\n  return e instanceof Error && e.message.startsWith(\"Expected sender to close the file\");\n}","tryCatchPattern":"catch (e) {\n  if (e instanceof Error && e.message.startsWith(\"Expected sender to close the file\")) {\n    // protocol divergence at file close: abort; the stray type in the message names the cause\n  }\n  throw e;\n}","preventionTips":["Implement the full per-file close handshake when writing a sender: close-sender in -> close-recipient out","Use unique code phrases per transfer to keep the control channel private","Pin peer versions; the close choreography is version-sensitive"],"tags":["protocol","state-machine","peer-mismatch"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}