Hmbown/CodeWhale · error

The previous pet recording is not a bounded regular file…

Error message

The previous pet recording is not a bounded regular file; it was preserved.

What it means

In resume mode, when the recording file already exists, createPetRecorder lstats it and requires it to be a regular file of at most 64 MiB before opening it for append. If the existing path is a directory, symlink, device node, or a regular file already larger than 64 MiB, the library refuses to touch it and throws, leaving the previous recording preserved and unmodified.

Solutions

  1. Move or remove the offending entry at the path (e.g. mv the oversized recording aside) and start a fresh recording without resume, or resume under a new path.
  2. Check the existing path first: lstat it and confirm it is a regular file with size <= 64 MiB before calling with resume: true.
  3. If old oversized recordings are common, rotate or truncate them offline (e.g. keep the last 64 MiB of complete JSONL rows) before resuming.

Example fix

// before
await createPetRecorder(outPath, { resume: true }); // throws if outPath is a dir or >64MiB
// after
const st = await lstat(outPath).catch(e => null);
if (st && (!st.isFile() || st.size > 64 * 1024 * 1024)) outPath = outPath + '.old-' + Date.now();
await createPetRecorder(outPath, { resume: true });
Defensive patterns

Strategy: validation

Validate before calling

import { lstat } from 'node:fs/promises';
async function resumeable(p) {
  try { const st = await lstat(p, { bigint: true }); return st.isFile() && st.size <= 64n * 1024n * 1024n; }
  catch { return true; } // not present: fresh start is fine
}

Try / catch

try { await createPetRecorder(p, { resume: true }); } catch (e) { if (/not a bounded regular file/.test(e.message)) { await rename(p, p + '.unusable-' + Date.now()); await createPetRecorder(p, { resume: false }); } else throw e; }

Prevention

When it happens

Trigger: createPetRecorder(path, { resume: true }) where the resolved path already exists as: a directory; a symlink (O_NOFOLLOW would also block it); a FIFO/device; or a regular file larger than 64 MiB (e.g. produced by an older unbounded recorder version).

Common situations: Resuming after switching output to a path that is now a directory; upgrading from a pre-bounded version whose recordings exceeded 64 MiB; pointing the recorder at a socket/device path by mistake; a stale symlink left by an earlier tool.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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

Appendix: source

Thrown at pet/scripts/lib/pet-recorder.mjs:62

    throw error;
  }
}

/** Replaces the CLI's unbounded append-only output. Each complete segment is
 * replayable on its own; the same live pathname always holds the newest one. */
export async function createPetRecorder(path, { maxBuckets = 216_000, maxBytes = 64 * 1024 * 1024, report = () => {}, resume = false } = {}) {
  if (!Number.isSafeInteger(maxBuckets) || maxBuckets < 1 || maxBuckets > 216_000
    || !Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > 64 * 1024 * 1024)
    throw new Error('Invalid pet recording segment limit.');
  path = resolve(await realpath(dirname(resolve(path))), basename(path));
  let lock = await lockRecorder(path), output, sequence = 0, bytes = 0, segment = 0, busy = false, restart = false, expectedMtime;
  try {
    try { output = await open(path, 'wx', 0o600); }
    catch (error) {
      if (!resume || error.code !== 'EEXIST') throw error;
      const original = await lstat(path, { bigint: true });
      if (!original.isFile() || original.size > 64n * 1024n * 1024n)
        throw new Error('The previous pet recording is not a bounded regular file; it was preserved.');
      output = await open(path, constants.O_RDWR | constants.O_APPEND | constants.O_NOFOLLOW | constants.O_NONBLOCK);
      const held = await output.stat({ bigint: true });
      if (held.dev !== original.dev || held.ino !== original.ino || held.size !== original.size)
        throw new Error('The previous pet recording changed while opening; it was preserved.');
      // Read at most the size already checked, including a single growth byte.
      const contents = Buffer.alloc(Number(held.size) + 1);
      let length = 0;
      while (length < contents.length) {
        const { bytesRead } = await output.read(contents, length, contents.length - length, length);
        if (!bytesRead) break;
        length += bytesRead;
      }
      if (length !== Number(held.size)) throw new Error('The previous pet recording changed while reading; it was preserved.');
      const text = new TextDecoder('utf-8', { fatal: true }).decode(contents.subarray(0, length));
      if (text && !text.endsWith('\n')) throw new Error('The previous pet recording has an incomplete final row; it was preserved.');
      decodePetJSONL(text);
      const unchanged = await output.stat({ bigint: true });
      if (unchanged.size !== original.size || unchanged.mtimeNs !== original.mtimeNs)

View on GitHub (pinned to 433685b202)