schollz/croc · error · Error

Expected recipient to close the file, got

Error message

Expected recipient to close the file, got ${closed.t}

What it means

Thrown by the sender when, after sending all chunks of a file, the next control message is neither 'error' nor 'close-sender'. The recipient protocol requires it to acknowledge each completed file with close-sender; anything else (e.g. 'recipientready', 'finished', 'pake') means the recipient state machine is out of step with the sender's, so the sender aborts instead of desynchronizing further.

Solutions

  1. Log the unexpected message type (already embedded in the message) and compare against the expected choreography: recipientready -> data -> close-sender -> close-recipient
  2. Ensure both peers run compatible versions of this web client / croc protocol; upgrade both sides
  3. Use a fresh, unique code phrase per transfer to avoid a third peer joining the room
  4. If writing a mock recipient, implement the full close-sender/close-recipient exchange per file
Defensive patterns

Strategy: try-catch

Type guard

function isUnexpectedCloseMessage(e: unknown): boolean {
  return e instanceof Error && e.message.startsWith("Expected recipient to close the file");
}

Try / catch

catch (e) {
  if (e instanceof Error && e.message.startsWith("Expected recipient to close the file")) {
    // log e.message — it names the stray message type; abort, never continue the loop
  }
  throw e;
}

Prevention

When it happens

Trigger: Recipient skips the close-sender handshake (buggy or older implementation); recipient sends the next recipientready before acknowledging the current file; a relay or proxy injects/reorders control frames; recipient crashes and another peer reuses the room, sending an unexpected control message.

Common situations: Interoperating with a modified croc CLI or non-standard client that omits the per-file close handshake; version drift where the close choreography changed; testing with a mock recipient that only implements the happy path; room collisions from reusing a short code phrase.

Related errors


AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15). Data as JSON: /api/errors/1dcd53c10d7f996c. Report an issue: GitHub.

Appendix: source

Thrown at web/src/protocol/client.ts:513

        key,
        (fileBytes) => {
          totalTransferred = beforeFile + fileBytes;
          callbacks.onProgress?.({
            fileIndex,
            fileCount: files.length,
            fileName: prepared.name,
            fileBytes,
            fileSize: prepared.size,
            totalBytes: totalTransferred,
            totalSize,
          });
        },
        signal,
      );
      const closed = await receiveControl(control, key);
      if (closed.t === "error") throw new Error(closed.m || "Recipient cancelled");
      if (closed.t !== "close-sender") {
        throw new Error(`Expected recipient to close the file, got ${closed.t}`);
      }
      await sendControl(control, { t: "close-recipient" }, key);
      callbacks.onFileComplete?.(prepared.name);
    }
  } catch (error) {
    await reportPeerError(control, key, error);
    throw error;
  } finally {
    closeAll(control, data);
  }
}

type ActiveReceive = {
  file: OfferedFile;
  sink: ReceiveSink;
  received: Set<number>;
  bytes: number;
  queue: Promise<void>;

View on GitHub (pinned to e25f1bdc04)