{"record":{"id":"b8dde651f3595806","repo":"Hmbown/CodeWhale","slug":"the-previous-pet-recording-is-not-a-bounded-regular-file-it","errorCode":null,"errorMessage":"The previous pet recording is not a bounded regular file; it was preserved.","messagePattern":"The previous pet recording is not a bounded regular file; it was preserved\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pet/scripts/lib/pet-recorder.mjs","lineNumber":62,"sourceCode":"    throw error;\n  }\n}\n\n/** Replaces the CLI's unbounded append-only output. Each complete segment is\n * replayable on its own; the same live pathname always holds the newest one. */\nexport async function createPetRecorder(path, { maxBuckets = 216_000, maxBytes = 64 * 1024 * 1024, report = () => {}, resume = false } = {}) {\n  if (!Number.isSafeInteger(maxBuckets) || maxBuckets < 1 || maxBuckets > 216_000\n    || !Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > 64 * 1024 * 1024)\n    throw new Error('Invalid pet recording segment limit.');\n  path = resolve(await realpath(dirname(resolve(path))), basename(path));\n  let lock = await lockRecorder(path), output, sequence = 0, bytes = 0, segment = 0, busy = false, restart = false, expectedMtime;\n  try {\n    try { output = await open(path, 'wx', 0o600); }\n    catch (error) {\n      if (!resume || error.code !== 'EEXIST') throw error;\n      const original = await lstat(path, { bigint: true });\n      if (!original.isFile() || original.size > 64n * 1024n * 1024n)\n        throw new Error('The previous pet recording is not a bounded regular file; it was preserved.');\n      output = await open(path, constants.O_RDWR | constants.O_APPEND | constants.O_NOFOLLOW | constants.O_NONBLOCK);\n      const held = await output.stat({ bigint: true });\n      if (held.dev !== original.dev || held.ino !== original.ino || held.size !== original.size)\n        throw new Error('The previous pet recording changed while opening; it was preserved.');\n      // Read at most the size already checked, including a single growth byte.\n      const contents = Buffer.alloc(Number(held.size) + 1);\n      let length = 0;\n      while (length < contents.length) {\n        const { bytesRead } = await output.read(contents, length, contents.length - length, length);\n        if (!bytesRead) break;\n        length += bytesRead;\n      }\n      if (length !== Number(held.size)) throw new Error('The previous pet recording changed while reading; it was preserved.');\n      const text = new TextDecoder('utf-8', { fatal: true }).decode(contents.subarray(0, length));\n      if (text && !text.endsWith('\\n')) throw new Error('The previous pet recording has an incomplete final row; it was preserved.');\n      decodePetJSONL(text);\n      const unchanged = await output.stat({ bigint: true });\n      if (unchanged.size !== original.size || unchanged.mtimeNs !== original.mtimeNs)","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/scripts/lib/pet-recorder.mjs#L44-L80","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nawait createPetRecorder(outPath, { resume: true }); // throws if outPath is a dir or >64MiB\n// after\nconst st = await lstat(outPath).catch(e => null);\nif (st && (!st.isFile() || st.size > 64 * 1024 * 1024)) outPath = outPath + '.old-' + Date.now();\nawait createPetRecorder(outPath, { resume: true });","handlingStrategy":"validation","validationCode":"import { lstat } from 'node:fs/promises';\nasync function resumeable(p) {\n  try { const st = await lstat(p, { bigint: true }); return st.isFile() && st.size <= 64n * 1024n * 1024n; }\n  catch { return true; } // not present: fresh start is fine\n}","typeGuard":null,"tryCatchPattern":"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; }","preventionTips":["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."],"tags":["filesystem","resume","validation","nodejs"],"backgroundTag":"incompatible-source-type","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}