Hmbown/CodeWhale · error · Error
Missing Engine .
Error message
Missing Engine ${field}. What it means
The `id()` helper inside observe() reads a required string field (`tool_call_id`, `tool_name`, or `id` depending on the event branch) and throws `Missing Engine <field>.` when the field is absent, not a string, or an empty string. The event passed the general validation but is missing the specific identifier the event's switch branch requires.
Solutions
- Populate the required id field for the event kind being emitted (check the switch branch in pet-engine.ts to see which field it needs).
- Replace empty strings with a real identifier before calling observe(), or skip the event.
- If your upstream uses a different key name, map it to the engine's expected field (e.g. callId → tool_call_id).
- Log the full event object on this error to see which `field` name appears in the message — it names exactly what is missing.
Example fix
// before
engine.observe({ event: 'tool_started', tool_name: '' }, now);
// after
engine.observe({ event: 'tool_started', tool_name: 'web_search', tool_call_id: callId }, now); Defensive patterns
Strategy: validation
Validate before calling
function hasRequiredIds(e: Record<string, unknown>): boolean {
const need = e.event === 'tool' || e.event === 'tool_finished' ? ['tool_call_id','tool_name'] : ['id'];
return need.every(k => typeof e[k] === 'string' && e[k].length > 0);
}
if (!hasRequiredIds(event)) throw new TypeError('event missing required id field'); Type guard
const hasId = (e: Record<string, unknown>, field: string): e is Record<string, unknown> & { [k: string]: string } =>
typeof e[field] === 'string' && (e[field] as string).length > 0; Try / catch
try {
engine.observe(event, at);
} catch (err) {
const m = /^Missing Engine (.+)\.$/.exec(err instanceof Error ? err.message : '');
if (m) {
console.error(`event missing field '${m[1]}'`, event);
return; // drop or enqueue for repair
}
throw err;
} Prevention
- Carry provider-issued ids (tool_call_id, message id) through your whole pipeline; never drop them between layers.
- Treat empty string as missing — validate ids with a non-empty check, not just presence.
- Map your internal field names to the engine's expected names in one adapter.
When it happens
Trigger: Emitting a `message_finished`/`tool` event whose `id` or `tool_call_id` is undefined, null, or ''; passing `tool_name: ''` for a tool span start; omitting the id because the upstream pairing (tool_call → result) lost the identifier.
Common situations: Streaming providers that omit ids on partial chunks; renaming `tool_call_id` to `callId` in your own wrapper; empty-string ids when a template substituted a missing variable; forwarding events from a queue schema that made ids optional.
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 index.
- 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/43277e752675a8db.
Report an issue: GitHub.
Appendix: source
Thrown at pet/src/core/pet-engine.ts:86
next.sequence = this.sequence; next.lastTime = this.lastTime;
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;View on GitHub (pinned to 433685b202)