Hmbown/CodeWhale · error · Error

Invalid Engine batch.

Error message

Invalid Engine batch.

What it means

observeEngineBatch() applies a batch of engine metadata observations atomically: it validates the parsed JSON is an array of at most 64 events, clones the engine, applies each event to the clone, and only then swaps it in. A non-array payload or a batch larger than 64 events is rejected as an invalid batch.

Solutions

  1. Chunk pending observations into batches of at most 64 events before calling observeEngineBatch().
  2. Ensure the producer emits a JSON array of events, not a wrapped object.
  3. Fall back to calling observeEngine() per event if you cannot control batch size.
  4. Validate Array.isArray(events) && events.length <= 64 on the caller side before invoking.

Example fix

// before
bridge.observeEngineBatch(JSON.stringify(allPendingEvents)); // 200 events: throws
// after
for (let i = 0; i < allPendingEvents.length; i += 64) {
  bridge.observeEngineBatch(JSON.stringify(allPendingEvents.slice(i, i + 64)), timeMs);
}
Defensive patterns

Strategy: validation

Validate before calling

function canObserveBatch(metadataJSON) {
  try {
    const v = JSON.parse(metadataJSON);
    return Array.isArray(v) && v.length <= 64;
  } catch { return false; }
}

Type guard

const isEngineBatch = (v) => Array.isArray(v) && v.length >= 0 && v.length <= 64;

Try / catch

try {
  bridge.observeEngineBatch(json, timeMs);
} catch (e) {
  if (e.message === 'Invalid Engine batch.') {
    for (const ev of JSON.parse(json)) bridge.observeEngine(JSON.stringify(ev), timeMs);
  } else throw e;
}

Prevention

When it happens

Trigger: observeEngineBatch(metadataJSON, timeMs) called with JSON that parses to an object or scalar instead of an array, or an array with more than 64 events (e.g. flushing a long queue of pending observations in one call).

Common situations: Buffering engine events and flushing without chunking; a producer changing the payload shape from array to {events: [...]} after an API update; passing the raw metadata object instead of a JSON array of events.

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


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/01140656a1a6e6d5. Report an issue: GitHub.

Appendix: source

Thrown at pet/ios/Resources/pet-native.js:97

        this.liveTape.reset();
        this.segment = undefined;
        this.engineTick = Math.round(this.world.frame.timeMs * 30 / 1000);
        this.engine = new pet_engine_js_1.PetEngineTelemetry();
    }
    /** Resume a live host at the first unrecorded bucket. Keep every accepted
     * interval, but never present its last observed frame as current evidence. */
    resumeEngine() {
        this.world.resumeObservation();
        this.engineTick = Math.round(this.world.frame.timeMs * 30 / 1000);
        this.engine = new pet_engine_js_1.PetEngineTelemetry();
        this.world.voices = [];
        return this.world.frame.timeMs;
    }
    observeEngine(metadataJSON, timeMs) { this.engine.observe(JSON.parse(metadataJSON), timeMs); }
    observeEngineBatch(metadataJSON, timeMs) {
        const events = JSON.parse(metadataJSON);
        if (!Array.isArray(events) || events.length > 64)
            throw new Error('Invalid Engine batch.');
        const next = this.engine.clone();
        for (const event of events)
            next.observe(event, timeMs);
        this.engine = next;
    }
    advanceEngine(timeMs, motion, waiting) {
        const target = Math.floor(timeMs * 30 / 1000 + 1e-8);
        if (!Number.isFinite(timeMs) || target < this.engineTick || target - this.engineTick > 300)
            throw new Error('Engine pet clock jump.');
        this.engine.confirmWaiting(timeMs, waiting);
        const voices = [];
        while (this.engineTick < target) {
            if ((this.engineTick + 1) % 12 === 0)
                this.world.acceptTelemetry(this.engine.bucket(Math.floor(this.engineTick / 12)));
            this.world.step(1 / 30, { motion, sensitivity: 1 });
            voices.push(...this.world.voices);
            this.engineTick++;
        }

View on GitHub (pinned to 433685b202)