{"record":{"id":"9e0c1616811899e5","repo":"Hmbown/CodeWhale","slug":"invalid-engine-pet-metadata-9e0c16","errorCode":null,"errorMessage":"Invalid Engine pet metadata.","messagePattern":"Invalid Engine pet metadata\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/src/core/pet-engine.ts","lineNumber":74,"sourceCode":"\n  /** Transactional batch copy; failed validation cannot accept half a packet. */\n  clone(): PetEngineTelemetry {\n    const next = new PetEngineTelemetry();\n    const copy = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T;\n    next.events = copy(this.events);\n    const spans = new Map(next.events.map(event => [event.id, event]));\n    // Active and waiting spans must still reference their journal entry so a\n    // later heartbeat extends the coverage consumed by bucket().\n    const span = (event: WhaleEvent) => spans.get(event.id) ?? copy(event);\n    next.active = new Map(Array.from(this.active, ([key, event]) => [key, span(event)]));\n    next.waiting = this.waiting ? span(this.waiting) : undefined;\n    next.sequence = this.sequence; next.lastTime = this.lastTime;\n    return next;\n  }\n\n  observe(value: unknown, at: number): void {\n    if (!Number.isFinite(at) || at < this.lastTime || at > PET_MAX_SECONDS * 1000) throw new Error('Invalid Engine pet clock.');\n    if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid Engine pet metadata.');\n    const e = value as Record<string, unknown>;\n    const allowed = ['event', 'index', 'channel', 'tool_call_id', 'tool_name', 'id', 'worker_status', 'failed'];\n    if (Object.keys(e).some(k => !allowed.includes(k)) || typeof e.event !== 'string'\n      || Object.values(e).some(v => typeof v === 'string' && v.length > 4096)\n      || e.channel !== undefined && !['text', 'reasoning'].includes(e.channel as string)\n      || ['tool_call_id', 'tool_name', 'id', 'worker_status'].some(k => e[k] !== undefined && typeof e[k] !== 'string')\n      || e.failed !== undefined && typeof e.failed !== 'boolean'\n      || e.index !== undefined && (!Number.isSafeInteger(e.index) || (e.index as number) < 0))\n      throw new Error('Invalid Engine pet metadata fields.');\n    this.lastTime = at;\n    this.events = this.events.filter(span => span.endTime >= at - 12_800);\n    const id = (field: string) => { const s = e[field]; if (typeof s !== 'string' || !s) throw new Error(`Missing Engine ${field}.`); return s; };\n    const index = () => { if (!Number.isSafeInteger(e.index)) throw new Error('Missing Engine index.'); return String(e.index); };\n    const start = (key: string, name: string, category: Category, agentId?: string) => {\n      if (this.active.size >= 256 && !this.active.has(key)) throw new Error('Too many active Engine pet spans.');\n      this.active.set(key, this.add(name, category, at, agentId));\n    };\n    const finish = (key: string) => { this.pulse(key, at); this.active.delete(key); };","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/src/core/pet-engine.ts#L56-L92","documentation":"PetEngineTelemetry.observe() validates every incoming event payload before converting it into spans. This error means the `value` argument passed to observe was not a plain non-null object: it was null, undefined, a primitive, or an Array. The engine throws early so malformed telemetry never corrupts the span timeline.","triggerScenarios":"Calling engine.observe(null, t), observe(undefined, t), observe(42, t), observe('event', t), or observe([..], t) — any first argument that fails `value && typeof value === 'object' && !Array.isArray(value)`.","commonSituations":"Passing a parsed-JSON array of events instead of a single event; passing undefined because an upstream variable was never assigned; passing a class instance after JSON round-trip when it became a string; forwarding a Kafka/stream record whose payload field was never deserialized.","solutions":["Ensure you call observe() once per event object, not with an array of events — iterate and call observe per element.","Check the variable passed as `value` is actually defined and an object at the call site (log it before calling).","If events arrive as JSON strings, JSON.parse them first and confirm the result is a non-array object.","Wrap observe() in try/catch and drop (or report) malformed events instead of letting one bad record crash the telemetry loop."],"exampleFix":"// before\nevents.forEach(e => engine.observe(e, now)); // e may be null / an array\n\n// after\nif (e && typeof e === 'object' && !Array.isArray(e)) {\n  engine.observe(e, now);\n}","handlingStrategy":"type-guard","validationCode":"function canObserve(v: unknown): boolean {\n  return !!v && typeof v === 'object' && !Array.isArray(v);\n}\nif (!canObserve(event)) throw new TypeError('observe expects a plain object event');\nengine.observe(event, timestamp);","typeGuard":"const isPlainObject = (v: unknown): v is Record<string, unknown> =>\n  typeof v === 'object' && v !== null && !Array.isArray(v);","tryCatchPattern":"try {\n  engine.observe(event, at);\n} catch (err) {\n  if (err instanceof Error && err.message === 'Invalid Engine pet metadata.') {\n    console.warn('dropping malformed telemetry event', event);\n    return; // skip, don't crash the loop\n  }\n  throw err;\n}","preventionTips":["Never pass arrays of events to observe(); loop and call it once per event object.","Check for null/undefined at the call site before invoking observe().","Centralize event construction in one function so payloads cannot drift from the expected shape."],"tags":["validation","typescript","telemetry"],"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-22T11:17:16.035Z"}