{"record":{"id":"6a90829d8e5acee0","repo":"jackwener/OpenCLI","slug":"invalid-trace-maxagedays-maxagedays","errorCode":null,"errorMessage":"Invalid trace maxAgeDays: ${maxAgeDays}","messagePattern":"Invalid trace maxAgeDays: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/observation/retention.ts","lineNumber":61,"sourceCode":"  GB: 1024 ** 3,\n};\n\nexport function parseByteSize(value: string | number): number {\n  if (typeof value === 'number') {\n    if (!Number.isFinite(value) || value < 0) throw new Error(`Invalid byte size: ${value}`);\n    return Math.floor(value);\n  }\n  const match = value.trim().match(/^(\\d+(?:\\.\\d+)?)\\s*(B|KB|MB|GB)?$/i);\n  if (!match) throw new Error(`Invalid byte size: ${value}`);\n  const amount = Number(match[1]);\n  const unit = (match[2] ?? 'B').toUpperCase();\n  return Math.floor(amount * BYTES_UNITS[unit]);\n}\n\nexport function resolveTraceRetentionPolicy(input: TraceRetentionPolicyInput = {}): ResolvedTraceRetentionPolicy {\n  const maxAgeDays = input.maxAgeDays ?? DEFAULT_TRACE_RETENTION_POLICY.maxAgeDays;\n  const maxCountPerProfile = input.maxCountPerProfile ?? DEFAULT_TRACE_RETENTION_POLICY.maxCountPerProfile;\n  if (!Number.isFinite(maxAgeDays) || maxAgeDays < 0) throw new Error(`Invalid trace maxAgeDays: ${maxAgeDays}`);\n  if (!Number.isInteger(maxCountPerProfile) || maxCountPerProfile < 0) {\n    throw new Error(`Invalid trace maxCountPerProfile: ${maxCountPerProfile}`);\n  }\n  return {\n    maxAgeDays,\n    maxAgeMs: maxAgeDays * 24 * 60 * 60 * 1000,\n    maxCountPerProfile,\n    maxBytesPerProfile: parseByteSize(input.maxBytesPerProfile ?? DEFAULT_TRACE_RETENTION_POLICY.maxBytesPerProfile),\n  };\n}\n\nexport function traceExpiresAt(createdAt: string, policyInput: TraceRetentionPolicyInput = {}): string {\n  const policy = resolveTraceRetentionPolicy(policyInput);\n  const createdAtMs = Date.parse(createdAt);\n  const base = Number.isFinite(createdAtMs) ? createdAtMs : Date.now();\n  return new Date(base + policy.maxAgeMs).toISOString();\n}\n","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/src/observation/retention.ts#L43-L79","documentation":"resolveTraceRetentionPolicy() normalizes the trace retention policy and validates maxAgeDays before computing maxAgeMs. This Error is thrown when maxAgeDays (explicit or from the default policy) is not a finite number or is negative. It guarantees the resulting retention window is a sane, positive duration.","triggerScenarios":"Calling resolveTraceRetentionPolicy({ maxAgeDays: -30 }) or { maxAgeDays: NaN } / Infinity; also any caller (e.g. policy()) passing a numeric string or null-adjacent value that bypasses types via `as any` or JS usage.","commonSituations":"Env var like TRACE_MAX_AGE_DAYS='-1', a NaN from Number(undefined) in a JS caller, or a config migration that left the field negative or missing in a way that yields NaN.","solutions":["Set maxAgeDays to a finite non-negative number (e.g. 30).","Sanitize the input: Number(value) and check Number.isFinite before calling, defaulting on failure.","Omit maxAgeDays entirely to use DEFAULT_TRACE_RETENTION_POLICY.maxAgeDays."],"exampleFix":"// before\nresolveTraceRetentionPolicy({ maxAgeDays: Number(process.env.TRACE_MAX_AGE_DAYS) }) // NaN if unset\n// after\nconst days = Number(process.env.TRACE_MAX_AGE_DAYS);\nresolveTraceRetentionPolicy({ maxAgeDays: Number.isFinite(days) && days >= 0 ? days : undefined })","handlingStrategy":"validation","validationCode":"function isValidMaxAgeDays(v) {\n  return typeof v === 'number' && Number.isFinite(v) && v >= 0;\n}\n// const days = Number(env.TRACE_MAX_AGE_DAYS);\n// if (env.TRACE_MAX_AGE_DAYS !== undefined && !isValidMaxAgeDays(days)) throw new ConfigError(...);","typeGuard":"function isMaxAgeDays(v: unknown): v is number {\n  return typeof v === 'number' && Number.isFinite(v) && v >= 0;\n}","tryCatchPattern":"try {\n  return resolveTraceRetentionPolicy(input);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Invalid trace maxAgeDays:')) {\n    logger.warn(`Bad maxAgeDays ${String(input?.maxAgeDays)}, falling back to defaults`);\n    return resolveTraceRetentionPolicy({ ...input, maxAgeDays: undefined });\n  }\n  throw e;\n}","preventionTips":["Coerce env-var numbers with Number() and Number.isFinite before use; treat undefined as 'use default'.","Add zod/yup schema: z.number().finite().nonnegative() for maxAgeDays at config load time.","Avoid arithmetic that can yield NaN (e.g. days * MULTIPLIER where days is a string) before passing the value."],"tags":["validation","configuration","retention"],"backgroundTag":"invalid-config-value","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}