{"record":{"id":"6c90bab4dc162665","repo":"schollz/croc","slug":"unexpected-peer-message-message-t","errorCode":null,"errorMessage":"Unexpected peer message: ${message.t}","messagePattern":"Unexpected peer message: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/protocol/client.ts","lineNumber":480,"sourceCode":"    if (peerIP.t !== \"externalip\") throw new Error(\"Recipient did not secure the channel\");\n    await sendControl(control, { t: \"externalip\", m: relay.externalIP }, key);\n    await sendControl(control, {\n      t: \"fileinfo\",\n      b: textEncoder.encode(JSON.stringify(senderInfo(files))),\n    }, key);\n\n    let totalTransferred = 0;\n    for (;;) {\n      checkAbort(signal);\n      const message = await receiveControl(control, key);\n      if (message.t === \"error\") throw new Error(message.m || \"Recipient refused transfer\");\n      if (message.t === \"finished\") {\n        await sendControl(control, { t: \"finished\" }, key);\n        callbacks.onStatus?.(\"Transfer complete\");\n        return;\n      }\n      if (message.t !== \"recipientready\" || !message.b) {\n        throw new Error(`Unexpected peer message: ${message.t}`);\n      }\n\n      const request = JSON.parse(\n        textDecoder.decode(message.b),\n      ) as RemoteFileRequestWire;\n      const fileIndex = request.FilesToTransferCurrentNum;\n      const prepared = files[fileIndex];\n      if (!prepared) throw new Error(\"Recipient requested an unknown file\");\n      callbacks.onStatus?.(`Sending ${prepared.name}`);\n      const beforeFile = totalTransferred;\n      await sendFileData(\n        prepared,\n        request.CurrentFileChunkRanges,\n        data,\n        key,\n        (fileBytes) => {\n          totalTransferred = beforeFile + fileBytes;\n          callbacks.onProgress?.({","sourceCodeStart":462,"sourceCodeEnd":498,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/client.ts#L462-L498","documentation":"The sender's control loop accepts only 'error', 'finished', and 'recipientready' (with data b) messages after the file info is sent. Any other message type (or a recipientready missing its payload) throws with the offending type interpolated. This is the sender's guard against protocol divergence or a desynchronized control channel mid-transfer.","triggerScenarios":"A recipient implementation sending progress/ack frames the sender does not know; a 'recipientready' message with b unset; frames from a previous handshake left in the buffer after a reconnect.","commonSituations":"Custom croc forks adding new control message types; partial reconnects leaving stale frames; version skew introduced mid-stream.","solutions":["Ensure both ends run the same protocol build so only known message types are exchanged","On any failure, tear down and restart the transfer cleanly instead of reusing sockets","Log message.t from the error to identify which extension frame is being sent, and add handling or remove it"],"exampleFix":"// before (forked recipient sends an extra frame)\nawait sendControl(control, { t: \"progress\", pct: 50 }, key); // sender: Unexpected peer message: progress\n\n// after: only send types the protocol defines\nawait sendControl(control, { t: \"recipientready\", b: encodedRequest }, key);","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isHandledControlMessage(msg) {\n  return !!msg && (msg.t === \"error\" || msg.t === \"finished\" ||\n    (msg.t === \"recipientready\" && msg.b instanceof Uint8Array && msg.b.length > 0));\n}","tryCatchPattern":"try {\n  await sendFiles(opts);\n} catch (e) {\n  if (/^Unexpected peer message:/.test(e.message)) {\n    logProtocolDivergence(e.message);\n    showBanner(\"Transfer aborted: peer sent an unrecognized message. Ensure matching versions.\");\n    return;\n  }\n  throw e;\n}","preventionTips":["Do not add control message types without updating the sender's loop on both ends","Reuse sockets only within one clean handshake/transfer lifecycle","Log the offending message type in telemetry to catch version skew in the field"],"tags":["protocol","transfer","sender","state-machine"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}