Hmbown/CodeWhale · error · Error
Invalid Engine batch.
Error message
Invalid Engine batch.
What it means
observeEngineBatch(metadataJSON, timeMs) parses the metadata JSON and requires it to be an array of at most 64 engine events; anything else (non-array JSON, or an array longer than 64) throws 'Invalid Engine batch.' The batch is applied to a cloned engine so a failure never leaves the live engine partially updated.
Solutions
- Ensure the payload is a JSON array and chunk it to <= 64 events per call
- If you have a single event, call observeEngine(metadataJSON, timeMs) instead, or wrap it: JSON.stringify([event])
- Unwrap producer envelopes: pass obj.events rather than the wrapper object
- Validate Array.isArray(parsed) && parsed.length <= 64 before calling
Example fix
// before
pet.observeEngineBatch(JSON.stringify(event), now);
// after
const batch = Array.isArray(event) ? event : [event];
for (let i = 0; i < batch.length; i += 64) {
pet.observeEngineBatch(JSON.stringify(batch.slice(i, i + 64)), now);
} Defensive patterns
Strategy: validation
Validate before calling
function validBatch(json) {
let v; try { v = JSON.parse(json); } catch { return false; }
return Array.isArray(v) && v.length <= 64;
} Type guard
const isEngineBatch = (v: unknown): v is unknown[] => Array.isArray(v) && v.length <= 64;
Try / catch
try {
pet.observeEngineBatch(json, now);
} catch (err) {
if (err.message === 'Invalid Engine batch.') {
for (const e of chunk(toArray(json), 64)) pet.observeEngineBatch(JSON.stringify(e), now);
} else throw err;
} Prevention
- Chunk event queues at 64 before flushing
- Keep single-event path (observeEngine) and batch path clearly separated
- Unwrap producer envelopes before calling
When it happens
Trigger: Passing a single event object (not wrapped in an array) to observeEngineBatch; passing an array with more than 64 events; passing JSON that parses to null/string/number; malformed JSON also fails earlier in JSON.parse.
Common situations: Caller switching from observeEngine (single object) to observeEngineBatch without wrapping; batching an entire session's events at once instead of chunking; telemetry producer emitting a wrapped object like {events:[...]} that was passed through directly.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Invalid Engine batch.
- 1
- A pinned task provider requires an explicit model
- A positive pull request number is required
- A provider and page loader are required.
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/499ce48283c980b2.
Report an issue: GitHub.
Appendix: source
Thrown at pet/src/core/pet-native.ts:80
if (text.length > 8 * 1024 * 1024) throw new Error('Native habitat exceeds 8 MiB.');
const points = this.world.sim.p.map(p => [p.hx, p.hy] as [number, number]);
this.world = PetWorld.fromRecording(points, JSON.parse(text));
this.liveTape.reset();
this.segment = undefined; this.engineTick = Math.round(this.world.frame.timeMs * 30 / 1000); this.engine = new 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(): number {
this.world.resumeObservation();
this.engineTick = Math.round(this.world.frame.timeMs * 30 / 1000);
this.engine = new PetEngineTelemetry();
this.world.voices = [];
return this.world.frame.timeMs;
}
observeEngine(metadataJSON: string, timeMs: number): void { this.engine.observe(JSON.parse(metadataJSON), timeMs); }
observeEngineBatch(metadataJSON: string, timeMs: number): void {
const events: unknown = 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: number, motion: boolean, waiting: boolean): void {
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: PetVoice[] = [];
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++;
}
this.world.voices = voices;
}
terminal(width: number, height: number): string {View on GitHub (pinned to 433685b202)