Hmbown/CodeWhale · error · Error

Unknown category for

Error message

Unknown category for ${e.id}.

What it means

buildPyramid() maps each event's category to a fixed channel via CATEGORIES.indexOf(e.category). It throws this per-event error when the category string is not one of the known channel names, because the event could not be attributed to any pyramid channel.

Solutions

  1. Map the event's category to one of the library's supported CATEGORIES before calling buildPyramid (the error names the event id to find it).
  2. Check the exported CATEGORIES list and match exact casing/spelling.
  3. Add a fallback in your import layer that coerces unknown categories to a default channel (e.g. 'other') instead of passing them through.
  4. If this appeared after a library upgrade, re-export/normalize old traces to the new category names.

Example fix

// before
buildPyramid([{ id: 'a', startTime: 0, endTime: 10, category: 'Tool' }]);
// after
buildPyramid([{ id: 'a', startTime: 0, endTime: 10, category: 'tool' }]); // matches CATEGORIES exactly
Defensive patterns

Strategy: validation

Validate before calling

const KNOWN = new Set(CATEGORIES); // exported by signal module
const normalized = events.map(e => KNOWN.has(e.category) ? e : { ...e, category: 'other' });

Type guard

function hasKnownCategory(e: WhaleEvent): boolean { return CATEGORIES.includes(e.category); }

Try / catch

try { buildPyramid(events, duration); } catch (e) { const m = /Unknown category for (.+)\./.exec(e.message); if (m) { remapCategory(m[1], 'other'); buildPyramid(events, duration); } else throw e; }

Prevention

When it happens

Trigger: An event in the array has a category not present in the library's CATEGORIES list — a typo (e.g. 'Tool' vs 'tool'), a renamed channel in a newer library version, or a custom category invented by the caller.

Common situations: Importing traces from another tool whose category vocabulary differs (e.g. OTLP span names); a library upgrade that renamed a category while old recorded traces still use the old name; case-sensitivity mistakes ('LLM' vs 'llm').

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at pet/src/core/signal.ts:23

  return { binMs, length, onsets: new Float64Array(n), activeMs: new Float64Array(n),
    outputTokens: new Float64Array(n), cost: new Float64Array(n), errors: new Float64Array(n), peak: new Float64Array(n) };
}
const FIELDS = ['onsets', 'activeMs', 'outputTokens', 'cost', 'errors'] as const;
/** O(events + channels × bins), including long intervals. No span-length inner loop. */
export function buildPyramid(events: readonly WhaleEvent[], requestedDuration?: number, maxBins = 16_384): SignalPyramid {
  if (!Number.isInteger(maxBins) || maxBins < 16 || maxBins > 1_048_576) throw new Error('maxBins must be an integer in [16, 1048576].');
  let duration = requestedDuration ?? 1;
  for (const e of events) duration = Math.max(duration, e.endTime, e.startTime, e.status === 'error' ? errorOnsetOf(e) : 0);
  if (!Number.isFinite(duration) || duration < 0) throw new Error('Signal duration must be finite and nonnegative.');
  duration = Math.max(1, duration);
  const binMs = 2 ** Math.ceil(Math.log2(Math.max(1, duration / (maxBins - 1))));
  const length = Math.floor(duration / binMs) + 1, fine = emptyLevel(length, binMs);
  const stride = length + 1;
  const activeDiff = new Float64Array(CATEGORIES.length * stride), tokenDiff = new Float64Array(CATEGORIES.length * stride);
  for (const e of events) {
    if (!Number.isFinite(e.startTime) || !Number.isFinite(e.endTime) || e.startTime < 0 || e.endTime < e.startTime) throw new Error(`Invalid interval for ${e.id}.`);
    const channel = CATEGORIES.indexOf(e.category);
    if (channel < 0) throw new Error(`Unknown category for ${e.id}.`);
    const a = Math.floor(e.startTime / binMs), b = Math.floor(e.endTime / binMs);
    const at = channel * length, diff = channel * stride;
    fine.onsets[at + a]++;
    fine.cost[at + a] += e.cost ?? 0;
    if (e.status === 'error') fine.errors[at + Math.floor(errorOnsetOf(e) / binMs)]++;
    const d = e.endTime - e.startTime, tokens = e.outputTokens ?? 0;
    if (d === 0) { fine.outputTokens[at + a] += tokens; continue; }
    if (a === b) { fine.activeMs[at + a] += d; fine.outputTokens[at + a] += tokens; }
    else {
      const left = (a + 1) * binMs - e.startTime, right = e.endTime - b * binMs, tokenRate = tokens / d;
      fine.activeMs[at + a] += left;
      fine.activeMs[at + b] += right;
      fine.outputTokens[at + a] += left * tokenRate;
      fine.outputTokens[at + b] += right * tokenRate;
      if (b > a + 1) {
        activeDiff[diff + a + 1] += binMs; activeDiff[diff + b] -= binMs;
        tokenDiff[diff + a + 1] += binMs * tokenRate; tokenDiff[diff + b] -= binMs * tokenRate;
      }

View on GitHub (pinned to 433685b202)