schollz/croc · error · Error

Message is too large (${payload.byteLength} bytes)

Error message

Message is too large (${payload.byteLength} bytes)

What it means

encodeFrame() wraps a payload in croc framing (4-byte magic 'croc' + little-endian uint32 length + payload) and refuses payloads larger than MAX_FRAME_SIZE (64 MiB). The length field is uint32, and the cap bounds memory use on both encode and decode; anything larger cannot be framed.

Source

Thrown at web/src/protocol/framing.ts:8

import { concatBytes } from "./bytes";

export const MAX_FRAME_SIZE = 64 * 1024 * 1024;
const MAGIC = new Uint8Array([0x63, 0x72, 0x6f, 0x63]);

export function encodeFrame(payload: Uint8Array) {
  if (payload.byteLength > MAX_FRAME_SIZE) {
    throw new Error(`Message is too large (${payload.byteLength} bytes)`);
  }
  const frame = new Uint8Array(8 + payload.byteLength);
  frame.set(MAGIC, 0);
  new DataView(frame.buffer).setUint32(4, payload.byteLength, true);
  frame.set(payload, 8);
  return frame;
}

export class FrameDecoder {
  private buffer = new Uint8Array();

  push(chunk: Uint8Array) {
    this.buffer =
      this.buffer.byteLength === 0 ? chunk.slice() : concatBytes(this.buffer, chunk);
    const messages: Uint8Array[] = [];

    while (this.buffer.byteLength >= 8) {
      for (let index = 0; index < MAGIC.byteLength; index += 1) {

View on GitHub (pinned to e25f1bdc04)

Solutions

  1. Split the payload into chunks of at most MAX_FRAME_SIZE and frame each chunk separately, reassembling on the receiver via FrameDecoder.
  2. Check payload.byteLength before calling encodeFrame and fail with your own actionable error message.
  3. If a larger cap is genuinely required in a controlled deployment, raise MAX_FRAME_SIZE on BOTH ends (encodeFrame and FrameDecoder share it) and keep it below 2^32.
  4. Compress the payload first (the codec already supports wasm.compress) and re-check the size.

Example fix

// before
const frame = encodeFrame(payload); // payload may be 100 MiB
// after
const CHUNK = 8 * 1024 * 1024;
for (let off = 0; off < payload.byteLength; off += CHUNK) {
  const frame = encodeFrame(payload.subarray(off, off + CHUNK));
  send(frame);
}
Defensive patterns

Strategy: validation

Validate before calling

const MAX_FRAME_SIZE = 64 * 1024 * 1024;
function assertFramable(payload: Uint8Array): void {
  if (payload.byteLength > MAX_FRAME_SIZE) {
    throw new RangeError(`payload ${payload.byteLength} exceeds frame cap; split into <=${MAX_FRAME_SIZE} chunks`);
  }
}

Try / catch

try {
  send(encodeFrame(payload));
} catch (error) {
  if (error instanceof RangeError || /too large/.test(String(error))) {
    await sendChunked(payload, 8 * 1024 * 1024); // fall back to chunked send
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling encodeFrame(payload) where payload.byteLength > 64 * 1024 * 1024. Typically caused by sending a single huge chunk, an un-split file block, or a bulk base64 blob through the relay instead of chunking at the application layer.

Common situations: Raising the application chunk size above 64 MiB; serializing a whole file's metadata plus data in one message; compressing already-incompressible data that still exceeds the cap; porting the Go croc client's larger frame limit without matching MAX_FRAME_SIZE.

Related errors


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