Hmbown/CodeWhale · error · Error

Missing Engine index.

Error message

Missing Engine index.

What it means

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.

Solutions

  1. Always include `index` as a non-negative safe integer on message/span events that require it.
  2. Coerce numeric strings with Number(i) and check Number.isSafeInteger before calling observe().
  3. If the event genuinely has no index, use a different event kind whose branch does not call index().
  4. Verify ordering: if indexes are assigned asynchronously, assign them before dispatching to observe(), not inside a callback.

Example fix

// before
engine.observe({ event: 'message_started' }, now); // no index

// after
engine.observe({ event: 'message_started', index: messageCounter++ }, now);
Defensive patterns

Strategy: validation

Validate before calling

function hasIndex(e: Record<string, unknown>): boolean {
  return Number.isSafeInteger(e.index) && (e.index as number) >= 0;
}
if (!hasIndex(event)) throw new TypeError('event requires a non-negative safe-integer index');

Type guard

const hasValidIndex = (e: Record<string, unknown>): e is Record<string, unknown> & { index: number } =>
  typeof e.index === 'number' && Number.isSafeInteger(e.index) && e.index >= 0;

Try / catch

try {
  engine.observe(event, at);
} catch (err) {
  if (err instanceof Error && err.message === 'Missing Engine index.') {
    console.error('event missing index', event);
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/599761cc9ffdd43d. Report an issue: GitHub.

Appendix: source

Thrown at pet/src/core/pet-engine.ts:87

    return next;
  }

  observe(value: unknown, at: number): void {
    if (!Number.isFinite(at) || at < this.lastTime || at > PET_MAX_SECONDS * 1000) throw new Error('Invalid Engine pet clock.');
    if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid Engine pet metadata.');
    const e = value as Record<string, unknown>;
    const allowed = ['event', 'index', 'channel', 'tool_call_id', 'tool_name', 'id', 'worker_status', 'failed'];
    if (Object.keys(e).some(k => !allowed.includes(k)) || typeof e.event !== 'string'
      || Object.values(e).some(v => typeof v === 'string' && v.length > 4096)
      || e.channel !== undefined && !['text', 'reasoning'].includes(e.channel as string)
      || ['tool_call_id', 'tool_name', 'id', 'worker_status'].some(k => e[k] !== undefined && typeof e[k] !== 'string')
      || e.failed !== undefined && typeof e.failed !== 'boolean'
      || e.index !== undefined && (!Number.isSafeInteger(e.index) || (e.index as number) < 0))
      throw new Error('Invalid Engine pet metadata fields.');
    this.lastTime = at;
    this.events = this.events.filter(span => span.endTime >= at - 12_800);
    const id = (field: string) => { const s = e[field]; if (typeof s !== 'string' || !s) throw new Error(`Missing Engine ${field}.`); return s; };
    const index = () => { if (!Number.isSafeInteger(e.index)) throw new Error('Missing Engine index.'); return String(e.index); };
    const start = (key: string, name: string, category: Category, agentId?: string) => {
      if (this.active.size >= 256 && !this.active.has(key)) throw new Error('Too many active Engine pet spans.');
      this.active.set(key, this.add(name, category, at, agentId));
    };
    const finish = (key: string) => { this.pulse(key, at); this.active.delete(key); };
    switch (e.event) {
      case 'turn_started': this.active.clear(); this.waiting = undefined; break;
      case 'message_started': start(`message:${index()}`, 'assistant_message', 'communication'); this.waiting = undefined; break;
      case 'thinking_started': start(`thinking:${index()}`, 'thinking', 'reasoning'); this.waiting = undefined; break;
      case 'response_delta': {
        const reasoning = e.channel === 'reasoning';
        const key = `${reasoning ? 'thinking' : 'message'}:${index()}`;
        if (!this.active.has(key)) start(key, reasoning ? 'thinking' : 'assistant_message', reasoning ? 'reasoning' : 'communication');
        else this.pulse(key, at);
        this.waiting = undefined; break;
      }
      case 'message_complete': finish(`message:${index()}`); break;
      case 'thinking_complete': finish(`thinking:${index()}`); break;

View on GitHub (pinned to 433685b202)