Hmbown/CodeWhale · error · Error
Invalid Engine pet metadata.
Error message
Invalid Engine pet metadata.
What it means
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.
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.
Example fix
// before
events.forEach(e => engine.observe(e, now)); // e may be null / an array
// after
if (e && typeof e === 'object' && !Array.isArray(e)) {
engine.observe(e, now);
} Defensive patterns
Strategy: type-guard
Validate before calling
function canObserve(v: unknown): boolean {
return !!v && typeof v === 'object' && !Array.isArray(v);
}
if (!canObserve(event)) throw new TypeError('observe expects a plain object event');
engine.observe(event, timestamp); Type guard
const isPlainObject = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);
Try / catch
try {
engine.observe(event, at);
} catch (err) {
if (err instanceof Error && err.message === 'Invalid Engine pet metadata.') {
console.warn('dropping malformed telemetry event', event);
return; // skip, don't crash the loop
}
throw err;
} Prevention
- 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.
When it happens
Trigger: 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)`.
Common situations: 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.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Duplicate .
- Invalid Engine pet clock.
- Invalid Engine pet metadata.
- Invalid Engine pet metadata fields.
- Invalid Engine pet metadata fields.
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/9e0c1616811899e5.
Report an issue: GitHub.
Appendix: source
Thrown at pet/src/core/pet-engine.ts:74
/** Transactional batch copy; failed validation cannot accept half a packet. */
clone(): PetEngineTelemetry {
const next = new PetEngineTelemetry();
const copy = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T;
next.events = copy(this.events);
const spans = new Map(next.events.map(event => [event.id, event]));
// Active and waiting spans must still reference their journal entry so a
// later heartbeat extends the coverage consumed by bucket().
const span = (event: WhaleEvent) => spans.get(event.id) ?? copy(event);
next.active = new Map(Array.from(this.active, ([key, event]) => [key, span(event)]));
next.waiting = this.waiting ? span(this.waiting) : undefined;
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); };View on GitHub (pinned to 433685b202)