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

  1. Unwrap arrays and call observe once per element
  2. Check the value is a non-null, non-array object before calling observe
  3. 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

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


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)