Hmbown/CodeWhale · error · Error

Invalid Engine pet clock.

Error message

Invalid Engine pet clock.

What it means

PetEngineTelemetry.observe validates the observation timestamp before accepting any event. It throws 'Invalid Engine pet clock.' when at is not finite, is earlier than the last accepted timestamp (monotonic clock), or exceeds PET_MAX_SECONDS * 1000 (the simulation's maximum horizon in milliseconds). The pet clock must move forward and stay inside the sim horizon so bucketing stays deterministic.

Solutions

  1. Sort events by timestamp before feeding them to observe()
  2. Use a single monotonic time source (performance.now()-style ms) for all observations
  3. Create a fresh PetEngineTelemetry instance for a new/replayed session instead of reusing one with a large lastTime
  4. Guard callers: skip or clamp timestamps that are not finite or fall outside [lastTime, PET_MAX_SECONDS*1000]

Example fix

// before
events.forEach(e => telemetry.observe(e.meta, e.atMs)); // unsorted, mixed clocks
// after
events.sort((a, b) => a.atMs - b.atMs).forEach(e => {
  const at = Math.round(e.atMs);
  if (Number.isFinite(at) && at >= telemetry.lastTime) telemetry.observe(e.meta, at);
});
Defensive patterns

Strategy: validation

Validate before calling

function canObserve(telemetry, at) {
  return Number.isFinite(at) && at >= telemetry.lastTime && at <= PET_MAX_SECONDS * 1000;
}
if (canObserve(telemetry, atMs)) telemetry.observe(meta, atMs);

Try / catch

try {
  telemetry.observe(meta, atMs);
} catch (err) {
  if (err.message === 'Invalid Engine pet clock.') {
    // drop or buffer the out-of-order/stale event; do not retry with a rewritten clock silently
    droppedStaleEvents.push({ meta, atMs });
  } else throw err;
}

Prevention

When it happens

Trigger: Passing a timestamp that went backwards (e.g. events delivered out of order, or performance.now() mixed with Date.now()); passing NaN/Infinity; passing a time beyond the pet sim's PET_MAX_SECONDS horizon; attaching a stale replay tape whose timestamps predate the instance's lastTime.

Common situations: Mixing wall-clock and monotonic time sources in the same telemetry stream; reusing a telemetry instance across session restores where the old lastTime exceeds the new stream's start; a replay with unsorted events; overflowing past the fixed simulation horizon in a very long session.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

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

    }
    /** Transactional batch copy; failed validation cannot accept half a packet. */
    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) => {

View on GitHub (pinned to 73e0f67d83)