{"record":{"id":"599761cc9ffdd43d","repo":"Hmbown/CodeWhale","slug":"missing-engine-index-pet","errorCode":null,"errorMessage":"Missing Engine index.","messagePattern":"Missing Engine index\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/src/core/pet-engine.ts","lineNumber":87,"sourceCode":"    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); };\n    switch (e.event) {\n      case 'turn_started': this.active.clear(); this.waiting = undefined; break;\n      case 'message_started': start(`message:${index()}`, 'assistant_message', 'communication'); this.waiting = undefined; break;\n      case 'thinking_started': start(`thinking:${index()}`, 'thinking', 'reasoning'); this.waiting = undefined; break;\n      case 'response_delta': {\n        const reasoning = e.channel === 'reasoning';\n        const key = `${reasoning ? 'thinking' : 'message'}:${index()}`;\n        if (!this.active.has(key)) start(key, reasoning ? 'thinking' : 'assistant_message', reasoning ? 'reasoning' : 'communication');\n        else this.pulse(key, at);\n        this.waiting = undefined; break;\n      }\n      case 'message_complete': finish(`message:${index()}`); break;\n      case 'thinking_complete': finish(`thinking:${index()}`); break;","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/src/core/pet-engine.ts#L69-L105","documentation":"The `index()` helper inside observe() converts the event's `index` field into the span key and throws `Missing Engine index.` when `index` is not a safe integer. Because the field-level validation earlier only checks `index` when it is defined (undefined is allowed there), this error fires at the point the event branch actually needs an index (e.g. message events) but the field was omitted or already failed validation.","triggerScenarios":"Emitting `message_started`/`message_finished` without an `index` field; passing `index` as a string ('0') or float; an earlier field-validation failure would throw a different message, so this specifically means index is absent or non-integer when the branch reads it.","commonSituations":"Forgetting to attach the message counter when synthesizing engine events from provider chunks; index lost when mapping over async results out of order; index supplied as a bigint or string after JSON round-trip through a system that turned numbers into strings.","solutions":["Always include `index` as a non-negative safe integer on message/span events that require it.","Coerce numeric strings with Number(i) and check Number.isSafeInteger before calling observe().","If the event genuinely has no index, use a different event kind whose branch does not call index().","Verify ordering: if indexes are assigned asynchronously, assign them before dispatching to observe(), not inside a callback."],"exampleFix":"// before\nengine.observe({ event: 'message_started' }, now); // no index\n\n// after\nengine.observe({ event: 'message_started', index: messageCounter++ }, now);","handlingStrategy":"validation","validationCode":"function hasIndex(e: Record<string, unknown>): boolean {\n  return Number.isSafeInteger(e.index) && (e.index as number) >= 0;\n}\nif (!hasIndex(event)) throw new TypeError('event requires a non-negative safe-integer index');","typeGuard":"const hasValidIndex = (e: Record<string, unknown>): e is Record<string, unknown> & { index: number } =>\n  typeof e.index === 'number' && Number.isSafeInteger(e.index) && e.index >= 0;","tryCatchPattern":"try {\n  engine.observe(event, at);\n} catch (err) {\n  if (err instanceof Error && err.message === 'Missing Engine index.') {\n    console.error('event missing index', event);\n    return;\n  }\n  throw err;\n}","preventionTips":["Attach the message/turn index synchronously when constructing the event, not in an async callback.","Coerce string indexes with Number() and verify with Number.isSafeInteger before observe().","Never pass bigint or float indexes; convert to a safe integer or skip the event."],"tags":["validation","missing-field","telemetry"],"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-23T08:17:48.524Z"}