Hmbown/CodeWhale · error · Error

Invalid Engine pet metadata fields.

Error message

Invalid Engine pet metadata fields.

What it means

After basic shape checks, observe validates each metadata field of the Engine event. It throws 'Invalid Engine pet metadata fields.' when any key is outside the allowed set ['event','index','channel','tool_call_id','tool_name','id','worker_status','failed'], event is not a string, any string value exceeds 4096 chars, channel is present but not 'text'/'reasoning', the id-ish fields are present but not strings, failed is present but not boolean, or index is present but not a non-negative safe integer.

Solutions

  1. Project the event to only the allowed keys before calling observe
  2. Whitelist any legitimately new protocol field in the allowed array (with the protocol change)
  3. Coerce index to Math.trunc and validate it is a non-negative safe integer; convert failed/channel types at the bridge boundary
  4. Log the offending event to identify which field violates the schema

Example fix

// before
telemetry.observe(rawEventMsg, at); // carries extra fields like 'text'
// after
const e = (({ event, index, channel, tool_call_id, tool_name, id, worker_status, failed }) => ({ event, index, channel, tool_call_id, tool_name, id, worker_status, failed }))(rawEventMsg);
telemetry.observe(e, at);
Defensive patterns

Strategy: validation

Validate before calling

const ALLOWED = ['event','index','channel','tool_call_id','tool_name','id','worker_status','failed'];
function projectEngineMeta(raw) {
  const e = {};
  for (const k of ALLOWED) if (raw[k] !== undefined) e[k] = raw[k];
  e.event = String(raw.event);
  return e;
}
telemetry.observe(projectEngineMeta(rawMsg), at);

Type guard

const hasValidFields = (e) => Object.keys(e).every(k => ALLOWED.includes(k)) && typeof e.event === 'string' && (e.index === undefined || (Number.isSafeInteger(e.index) && e.index >= 0));

Try / catch

try {
  telemetry.observe(meta, at);
} catch (err) {
  if (err.message === 'Invalid Engine pet metadata fields.') {
    console.warn('dropping malformed engine event', meta);
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Forwarding raw EventMsg JSON that includes extra fields (e.g. 'text', 'result', nested objects); index as a float or negative; channel misspelled; failed as 0/1 instead of boolean; tool_name longer than 4096 characters; event missing or not a string.

Common situations: A protocol version change adds a new EventMsg field not yet in the allowlist; a caller passes the full deserialized message including payloads instead of the projected metadata; JSON round-tripping turns an integer index into a float; snake_case vs camelCase mixing.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/39c1b7f498f8a0ae. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tui/pet_watch/pet-native.js:1740

        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.');
            this.active.set(key, this.add(name, category, at, agentId));
        };
        const finish = (key) => { this.pulse(key, at); this.active.delete(key); };
        switch (e.event) {
            case 'turn_started':
                this.active.clear();
                this.waiting = undefined;
                break;
            case 'message_started':

View on GitHub (pinned to 73e0f67d83)