{"record":{"id":"e94d33693a71a439","repo":"Hmbown/CodeWhale","slug":"too-many-active-engine-pet-spans-native","errorCode":null,"errorMessage":"Too many active Engine pet spans.","messagePattern":"Too many active Engine pet spans\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"pet/ios/Resources/pet-native.js","lineNumber":1749,"sourceCode":"            throw new Error('Invalid Engine pet metadata.');\n        const e = value;\n        const allowed = ['event', 'index', 'channel', 'tool_call_id', 'tool_name', 'id', 'worker_status', 'failed'];\n        if (Object.keys(e).some(k => !allowed.includes(k)) || typeof e.event !== 'string'\n            || Object.values(e).some(v => typeof v === 'string' && v.length > 4096)\n            || e.channel !== undefined && !['text', 'reasoning'].includes(e.channel)\n            || ['tool_call_id', 'tool_name', 'id', 'worker_status'].some(k => e[k] !== undefined && typeof e[k] !== 'string')\n            || e.failed !== undefined && typeof e.failed !== 'boolean'\n            || e.index !== undefined && (!Number.isSafeInteger(e.index) || e.index < 0))\n            throw new Error('Invalid Engine pet metadata fields.');\n        this.lastTime = at;\n        this.events = this.events.filter(span => span.endTime >= at - 12_800);\n        const id = (field) => { const s = e[field]; if (typeof s !== 'string' || !s)\n            throw new Error(`Missing Engine ${field}.`); return s; };\n        const index = () => { if (!Number.isSafeInteger(e.index))\n            throw new Error('Missing Engine index.'); return String(e.index); };\n        const start = (key, name, category, agentId) => {\n            if (this.active.size >= 256 && !this.active.has(key))\n                throw new Error('Too many active Engine pet spans.');\n            this.active.set(key, this.add(name, category, at, agentId));\n        };\n        const finish = (key) => { this.pulse(key, at); this.active.delete(key); };\n        switch (e.event) {\n            case 'turn_started':\n                this.active.clear();\n                this.waiting = undefined;\n                break;\n            case 'message_started':\n                start(`message:${index()}`, 'assistant_message', 'communication');\n                this.waiting = undefined;\n                break;\n            case 'thinking_started':\n                start(`thinking:${index()}`, 'thinking', 'reasoning');\n                this.waiting = undefined;\n                break;\n            case 'response_delta': {\n                const reasoning = e.channel === 'reasoning';","sourceCodeStart":1731,"sourceCodeEnd":1767,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/pet/ios/Resources/pet-native.js#L1731-L1767","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nfor (const ev of events) engine.observe(ev, ev.at);\n// after\nfor (const ev of events) {\n  if (engine.active.size >= 256 && ev.event === 'tool_call') ev = { ...ev, event: 'error', failed: true };\n  engine.observe(ev, ev.at);\n}","handlingStrategy":"try-catch","validationCode":"if (engine.active.size >= 256 && !engine.active.has(spanKey)) {\n  // close or drop stale spans before starting a new one\n}","typeGuard":null,"tryCatchPattern":"try {\n  engine.observe(ev, at);\n} catch (err) {\n  if (err.message === 'Too many active Engine pet spans.') {\n    console.warn('span leak: force-closing oldest spans');\n    engine.active.clear();\n    engine.observe(ev, at);\n  } else throw err;\n}","preventionTips":["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"],"tags":["resource-limit","state","spans"],"backgroundTag":"buffer-capacity-exceeded","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}