{"record":{"id":"c72690210c83b0c8","repo":"Hmbown/CodeWhale","slug":"missing-field-times-must-be-numeric-milliseconds","errorCode":null,"errorMessage":"Missing ${field}. Times must be numeric milliseconds.","messagePattern":"Missing (.+?)\\. Times must be numeric milliseconds\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/src/core/ingest.ts","lineNumber":66,"sourceCode":"  }\n  if (value && typeof value === 'object') seen.delete(value);\n  return hook ? hook(out, path) : out;\n}\nexport function privacyEvent(e: WhaleEvent, mode: PrivacyMode): WhaleEvent {\n  if (mode === 'metadata') {\n    const { payload: _payload, raw: _raw, ...rest } = e;\n    return { ...rest, links: e.links?.map(link=>({traceId:link.traceId,spanId:link.spanId})), attributes: Object.fromEntries(Object.entries(e.attributes).filter(([k, v]) =>\n      SAFE_META.test(k) && (typeof v !== 'object' || v === null)\n      || ['whalesong.container', 'codewhale.container', 'whalesong.waiting'].includes(k) && typeof v === 'boolean'\n      // Relative timestamps can be negative before the importer rebases them.\n      || k === 'whalesong.error_onset_ms' && typeof v === 'number' && Number.isFinite(v))) };\n  }\n  return e;\n}\nfunction number(v: unknown, field: string, optional = true): number | undefined {\n  if (v === undefined || v === null) {\n    if (optional) return undefined;\n    throw new Error(`Missing ${field}. Times must be numeric milliseconds.`);\n  }\n  if (typeof v !== 'number' || !Number.isFinite(v) || Math.abs(v) > Number.MAX_SAFE_INTEGER) throw new Error(`Invalid ${field}: expected a finite, safely representable number.`);\n  return v;\n}\nfunction nonnegative(v: unknown, field: string): number | undefined {\n  const n = number(v, field); if (n !== undefined && n < 0) throw new Error(`${field} must be nonnegative.`); return n;\n}\nfunction numericAttr(v: unknown): number | undefined {\n  if (v === undefined || v === null || v === '') return undefined;\n  const n = Number(v); return Number.isFinite(n) && n >= 0 ? n : undefined;\n}\nexport function categoryFor(name: string, a: Obj): Category {\n  const explicit = a['whalesong.category'] ?? a.category;\n  if (CATEGORIES.includes(explicit)) return explicit;\n  const n = name.toLowerCase(), op = String(a['gen_ai.operation.name'] ?? '').toLowerCase();\n  if (/exception|^error\\b/.test(n)) return 'error';\n  if (/spawn|fork|subagent/.test(n) || op === 'invoke_agent') return 'agent';\n  if (/message\\.send|handoff|agent\\.message/.test(n)) return 'communication';","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/src/core/ingest.ts#L48-L84","documentation":"The `number` helper in pet/src/core/ingest.ts:66 throws when a time field is undefined/null while the `optional` flag is false — i.e. the field is required. startTime is validated as required via number(a.startTime,'startTime',false).","triggerScenarios":"Calling the OTLP/trace ingest path with a record missing startTime (number(v,field,false)); nonnegative fields routed through required mode.","commonSituations":"OTLP span JSON missing startTimeUnixNano mapped to startTime; a transform dropping the field when null; hand-written test fixtures omitting startTime.","solutions":["Provide a numeric millisecond startTime on every ingested record","Check the upstream exporter mapping for null/undefined startTime","Default to span start if your source has it under a different key"],"exampleFix":"// before\n{name:'op', traceId:'t1', id:'e1'}\n// after\n{name:'op', traceId:'t1', id:'e1', startTime: 1700000000000}","handlingStrategy":"type-guard","validationCode":"if (rec.startTime===undefined||rec.startTime===null) throw new Error('startTime required before ingest');","typeGuard":"const hasStartTime = (r:any): r is {startTime:number} & Record<string,unknown> => typeof r?.startTime==='number' && Number.isFinite(r.startTime);","tryCatchPattern":"try { ingest(records); } catch (e) { if (/Missing .*\\. Times must be numeric/.test(e.message)) { /* repair or drop the record named by field/index */ } else throw e; }","preventionTips":["Require startTime in your record schema/validation layer","Map OTLP startTimeUnixNano explicitly to startTime","Reject null-filled records at export time"],"tags":["validation","times","missing-field","ingest"],"backgroundTag":"missing-required-argument","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}