Hmbown/CodeWhale · error · Error

Recording exceeds the 64 MiB export limit. The current…

Error message

Recording exceeds the 64 MiB export limit. The current world was kept.

What it means

Thrown by the save/export handler while incrementally serializing `world.recordingChunk(index)` chunks and accumulating their UTF-8 byte size. If the accumulated recording exceeds 64 MiB the export is aborted; the live world is untouched because the catch handler only updates the message text.

Solutions

  1. Shorten the session or reset the recording before exporting.
  2. Trim or sample the recorded tape to reduce size under the limit.
  3. Split the recording and export in parts if long captures are required.

Example fix

null
Defensive patterns

Strategy: try-catch

Validate before calling

let bytes = 0;
for (let i = 0; ; i++) { const c = world.recordingChunk(i); if (c === null) break; bytes += new TextEncoder().encode(c).length; }
if (bytes > 64 * 1024 * 1024) trimRecording();

Type guard

null

Try / catch

try { exportRecording(); } catch (e) { message.textContent = e.message; /* world remains usable */ }

Prevention

When it happens

Trigger: Clicking the `save` button after a recording has grown past 64 MiB of serialized JSON chunk data (long-running sessions with dense telemetry).

Common situations: Leaving the pet running for hours with a large tape; high-frequency interaction recording; exporting after importing a near-limit replay and letting it grow.

Understand the failure class

Background: "File too large" / "file size exceeds limit" errors: why libraries cap file sizes and how to fix them — this error's family across 46 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/e4df04bd749ff67d. Report an issue: GitHub.

Appendix: source

Thrown at pet/src/ui/pet.ts:217

        const traces = importTrace(text, file.name, { privacy: 'metadata' });
        if (traces.length !== 1) throw new Error('Choose one recording to import.');
        nextTape = compilePetTelemetry(traces[0].events, traces[0].duration);
      }
      next = new PetWorld(points, nextTape, [], 2, true);
    }
    if (!await mayLeave()) return;
    adoptImported(next, `Local replay · ${file.name}`);
    message.textContent = 'Recording loaded locally, including its current pose, interactions and score.';
  } catch (error) { message.textContent = error instanceof Error ? error.message : 'Unable to import this file.'; }
  finally { input.value = ''; }
};
get('save').onclick = () => {
  try {
    const chunks: string[] = []; let bytes = 0;
    for (let index = 0; ; index++) {
      const chunk = world.recordingChunk(index); if (chunk === null) break;
      bytes += new TextEncoder().encode(chunk).length;
      if (bytes > 64 * 1024 * 1024) throw new Error('Recording exceeds the 64 MiB export limit. The current world was kept.');
      chunks.push(chunk);
    }
    const blob = new Blob(chunks, { type: 'application/json' });
    const url = URL.createObjectURL(blob), a = document.createElement('a'); a.href = url; a.download = 'codewhale-pet-replay.json'; a.click(); setTimeout(() => URL.revokeObjectURL(url), 1000);
  } catch (error) { message.textContent = error instanceof Error ? error.message : 'Unable to export the recording. The current world was kept.'; }
};
get('follow').onclick = async () => {
  // A browser-granted read handle is the same local JSONL seam native hosts use.
  // It is never persisted: reloading restores the accepted recording, not access.
  const picker = (window as Window & { showOpenFilePicker?: () => Promise<{ getFile(): Promise<File> }[]> }).showOpenFilePicker;
  if (!picker) { message.textContent = 'This browser cannot follow local files. Import a tape to replay it, or use the native pet for live telemetry.'; return; }
  try {
    const [handle] = await picker.call(window); if (!handle) return;
    const file = await handle.getFile();
    if (!await mayLeave()) return;
    stopFollowing(); const ticket = liveGeneration;
    mode.value = 'live'; paused = false; get('pause').textContent = 'Pause'; get('pause').setAttribute('aria-pressed', 'false');
    get<HTMLButtonElement>('pause').disabled = true; seek.disabled = true;

View on GitHub (pinned to 433685b202)