{"record":{"id":"4e89a54e190a0f54","repo":"Hmbown/CodeWhale","slug":"invalid-pet-recording-segment-limit","errorCode":null,"errorMessage":"Invalid pet recording segment limit.","messagePattern":"Invalid pet recording segment limit\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pet/scripts/lib/pet-recorder.mjs","lineNumber":53,"sourceCode":"  };\n  try {\n    database = new DatabaseSync(name);\n    database.exec('PRAGMA busy_timeout = 0; BEGIN EXCLUSIVE');\n    await check();\n    return { check, close: () => { database.close(); } };\n  } catch (error) {\n    database?.close();\n    if (error.errcode === 5 || error.errcode === 6) throw new Error('Another pet recorder is using this output.');\n    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);","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/scripts/lib/pet-recorder.mjs#L35-L71","documentation":"createPetRecorder validates the caller-supplied segment limits before touching the filesystem: maxBuckets must be a safe integer in [1, 216000] and maxBytes must be a safe integer in [1, 64 MiB]. Any value outside those ranges (or a non-integer like NaN, Infinity, a float, or a string) throws this error immediately, because an unbounded or invalid limit would recreate the unbounded append-only output the recorder is designed to replace.","triggerScenarios":"Calling createPetRecorder(path, { maxBuckets: 0 }), a negative maxBytes, maxBuckets: 216001 (above cap), maxBytes: 64*1024*1024 + 1 (above 64 MiB cap), or non-safe-integers such as NaN, Infinity, 1000.5, or a numeric string parsed from CLI args/env without coercion.","commonSituations":"Config values read from environment variables or JSON left as strings instead of numbers; a caller passing 0 or undefined-typed placeholders to 'disable' rotation; copy-pasting the 64*1024*1024 default with a `+1` headroom tweak; computing limits with float arithmetic (e.g. bytes/1024/1024).","solutions":["Pass only safe integers: maxBuckets between 1 and 216000 and maxBytes between 1 and 67108864, or omit both to use the defaults (216000 buckets, 64 MiB).","If limits come from env/config, coerce with Number() and validate with Number.isSafeInteger plus range checks before calling createPetRecorder.","Log the actual computed values just before the call to spot float or string leakage; round or floor derived byte values."],"exampleFix":"// before\nawait createPetRecorder(path, { maxBytes: opts.maxBytes ?? '64MB' });\n// after\nconst maxBytes = Number(opts.maxBytes ?? 64 * 1024 * 1024);\nif (!Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > 64 * 1024 * 1024) throw new Error(`bad maxBytes: ${opts.maxBytes}`);\nawait createPetRecorder(path, { maxBytes });","handlingStrategy":"validation","validationCode":"function validLimits({ maxBuckets = 216000, maxBytes = 64 * 1024 * 1024 } = {}) {\n  return Number.isSafeInteger(maxBuckets) && maxBuckets >= 1 && maxBuckets <= 216000\n    && Number.isSafeInteger(maxBytes) && maxBytes >= 1 && maxBytes <= 64 * 1024 * 1024;\n}","typeGuard":"const isSafeIntInRange = (v, lo, hi) => Number.isSafeInteger(v) && v >= lo && v <= hi;","tryCatchPattern":"try { await createPetRecorder(p, opts); } catch (e) { if (e.message === 'Invalid pet recording segment limit.') { console.error('bad limits', opts); process.exitCode = 2; } else throw e; }","preventionTips":["Never pass raw env/CLI strings as limits; coerce with Number() and validate first.","Keep derived values integral: use Math.floor on computed byte counts.","Prefer omitting the options to rely on library defaults unless a custom bound is genuinely needed."],"tags":["validation","arguments","cli","nodejs"],"backgroundTag":"invalid-argument-value","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T21:17:16.096Z"}