Hmbown/CodeWhale · error · Error
Invalid Engine pet metadata.
Error message
Invalid Engine pet metadata.
What it means
PetEngineTelemetry.observe expects a plain object carrying Engine event metadata (an adapter view of codewhale_protocol::EventMsg). It throws 'Invalid Engine pet metadata.' when value is null, not an object, or an Array. This guards the transactional boundary: a malformed packet must be rejected whole rather than half-applied.
Solutions
- Unwrap arrays and call observe once per element
- Check the value is a non-null, non-array object before calling observe
- Fix the bridge/deserializer so missing events are skipped, not passed as null/undefined
Example fix
// before telemetry.observe(eventBatch, at); // array passed // after for (const e of eventBatch) if (e && typeof e === 'object' && !Array.isArray(e)) telemetry.observe(e, at);
Defensive patterns
Strategy: type-guard
Validate before calling
function isEngineEventMeta(v) {
return v !== null && typeof v === 'object' && !Array.isArray(v);
}
if (isEngineEventMeta(msg)) telemetry.observe(msg, at); Type guard
const isEventMeta = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
Try / catch
try {
telemetry.observe(value, at);
} catch (err) {
if (err.message === 'Invalid Engine pet metadata.') return; // skip null/non-object packet
throw err;
} Prevention
- Unwrap arrays at the batching layer before calling observe
- Handle null/undefined from Option-style bridges explicitly
- Type-check deserialized payloads before feeding telemetry
When it happens
Trigger: Calling observe(undefined/null), passing an array of events instead of one event object, or passing a primitive (string/number) where an event metadata object is expected.
Common situations: A caller batching events loops over the array but passes the array itself to observe(); a deserialize step returning undefined for a missing record; a Rust->JS bridge mapping an Option<EventMsg> to null.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- Codewhale terminal receipt contained a non-scalar field
- Fleet task ' ' metadata.coordination_contracts must contain…
- Invalid Engine pet clock.
- Invalid Engine pet metadata.
- Invalid Engine pet metadata fields.
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/dcccb5570af90ce0.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tui/pet_watch/pet-native.js:1731
clone() {
const next = new PetEngineTelemetry();
const copy = (value) => JSON.parse(JSON.stringify(value));
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) => 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, at) {
if (!Number.isFinite(at) || at < this.lastTime || at > pet_sim_js_1.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;
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)
|| ['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 < 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) => { 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, name, category, agentId) => {
if (this.active.size >= 256 && !this.active.has(key))
throw new Error('Too many active Engine pet spans.');View on GitHub (pinned to 73e0f67d83)