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
- 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.
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
- 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.
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
- Missing Engine .
- Missing Engine .
- Missing Engine index.
- Invalid Engine pet clock.
- Invalid Engine pet metadata.
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)