Hmbown/CodeWhale · error · Error
Too many active Engine pet spans.
Error message
Too many active Engine pet spans.
What it means
observe() tracks currently open activity spans in this.active (max 256). start() throws 'Too many active Engine pet spans.' when 256 spans are already open and a new distinct key arrives, bounding memory and catching leaked/unbalanced start events.
Solutions
- Ensure every start event has a matching finish (same key) in the observed stream
- Replay the log in order and verify start/finish pairing; fix key computation so finish keys match start keys
- Truncate or drop lowest-priority open spans before the 256 limit is hit
- Reset the observer (active.clear()) when starting a new replay
Example fix
// before
for (const ev of events) engine.observe(ev, ev.at);
// after
for (const ev of events) {
if (engine.active.size >= 256 && ev.event === 'tool_call') ev = { ...ev, event: 'error', failed: true };
engine.observe(ev, ev.at);
} Defensive patterns
Strategy: try-catch
Validate before calling
if (engine.active.size >= 256 && !engine.active.has(spanKey)) {
// close or drop stale spans before starting a new one
} Try / catch
try {
engine.observe(ev, at);
} catch (err) {
if (err.message === 'Too many active Engine pet spans.') {
console.warn('span leak: force-closing oldest spans');
engine.active.clear();
engine.observe(ev, at);
} else throw err;
} Prevention
- Guarantee start/finish pairing for every span key in the replay
- Use a deterministic key function shared by start and finish paths
- Force-close spans on turn_started/turn_complete boundaries
- Cap parallel agent fan-out below 256 or batch tool starts
When it happens
Trigger: 256+ concurrent open spans — e.g. tool_call start events whose matching completion events were never observed, or a massively parallel agent session opening more than 256 spans at once.
Common situations: Dropped/lost tool_call_end events in truncated logs so starts leak, an agent spawning more than 256 parallel tools, or keys computed inconsistently so finishes never match their starts.
Related errors
- Too many active Engine pet spans.
- A cancelled Operation cannot be edited.
- Archive the legacy recording before accepting more…
- DSH is already connected; use
- DSH is not connected; nothing to
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/e94d33693a71a439.
Report an issue: GitHub.
Appendix: source
Thrown at pet/ios/Resources/pet-native.js:1749
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':
start(`message:${index()}`, 'assistant_message', 'communication');
this.waiting = undefined;
break;
case 'thinking_started':
start(`thinking:${index()}`, 'thinking', 'reasoning');
this.waiting = undefined;
break;
case 'response_delta': {
const reasoning = e.channel === 'reasoning';View on GitHub (pinned to 433685b202)