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
- 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.
- Check the existing path first: lstat it and confirm it is a regular file with size <= 64 MiB before calling with resume: true.
- 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
- lstat the target before resuming; reject directories, symlinks, and >64MiB files.
- Point the recorder at a dedicated directory no other tool rotates or cleans.
- Check file sizes after long runs; an unbounded predecessor may have left oversized recordings.
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
- The previous pet recording changed while opening; it was…
- Automation lock must be a regular file
- cloud job id must look like cloud_
- Codewhale credentials path must be a directory
- CodewhalePet/1
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)