Hmbown/CodeWhale · error · Error

Invalid failure observation time.

Error message

Invalid failure observation time.

What it means

errorOnsetOf reads the whalesong.error_onset_ms attribute recording when a failure was observed on an event. The attribute is valid only if it is a finite number greater than or equal to the event's startTime; anything else (missing type, NaN/Infinity, or a value before the span began) throws, keeping failure timing trustworthy across all signal views.

Solutions

  1. Fix the producer to emit a finite number >= the event's startTime, or omit the attribute entirely (it then defaults to startTime).
  2. Clamp or drop bad attribute values before ingestion: if onset < startTime, set it equal to startTime or delete the key.
  3. Verify the attribute is not stringified during export/import (JSON numbers must stay numbers).
  4. Check for clock skew between the component writing onsets and the one starting spans; normalize clocks before stamping.

Example fix

// before
{ "attributes": { "whalesong.error_onset_ms": "1700000000000" } } // string
// after
{ "attributes": { "whalesong.error_onset_ms": 1700000000000 } }
Defensive patterns

Strategy: type-guard

Validate before calling

const onset = e.attributes['whalesong.error_onset_ms'];
if (onset !== undefined && (typeof onset !== 'number' || !Number.isFinite(onset) || onset < e.startTime)) {
  delete e.attributes['whalesong.error_onset_ms']; // or clamp to e.startTime
}

Type guard

function hasValidOnset(e: WhaleEvent): boolean {
  const t = e.attributes['whalesong.error_onset_ms'];
  return t === undefined || (typeof t === 'number' && Number.isFinite(t) && t >= e.startTime);
}

Try / catch

try {
  const onset = errorOnsetOf(event);
} catch (e) {
  if (e instanceof Error && e.message === 'Invalid failure observation time.') {
    console.warn('Bad error_onset_ms; falling back to event startTime.');
    const onset = event.startTime;
  }
}

Prevention

When it happens

Trigger: An event carries whalesong.error_onset_ms that is a string, NaN, Infinity, or a number smaller than e.startTime, and errorOnsetOf is invoked by prune, normalized, snapshot, importTrace, events, or buildPyramid.

Common situations: A producer emitting the attribute before computing the span start; serializing the attribute to a string; clock skew between the failure recorder and span starter; hand-edited bundles setting negative or bogus onsets.

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/2177ffc21a2703bd. Report an issue: GitHub.

Appendix: source

Thrown at pet/src/core/model.ts:88

  if (f.model && e.model !== f.model) return false;
  if (f.tool && e.tool !== f.tool) return false;
  if (f.status && e.status !== f.status) return false;
  if (f.query) {
    const q = f.query.toLowerCase();
    // Search is deliberately content-aware but runs only over locally retained fields.
    if (![e.name, e.id, e.agentId, e.model, e.tool, e.provider, e.category,
      JSON.stringify(e.attributes), JSON.stringify(e.payload), JSON.stringify(e.raw)].filter(Boolean).join(' ').toLowerCase().includes(q)) return false;
  }
  return true;
}
export const durationOf = (e: WhaleEvent): number => Math.max(0, e.endTime - e.startTime);
/** An explicit failure receipt can arrive after a span began or ended. Keep
 * its timestamp distinct from the operation onset in every signal view. */
export function errorOnsetOf(e: WhaleEvent): number {
  const time = e.attributes['whalesong.error_onset_ms'];
  if (time === undefined) return e.startTime;
  if (typeof time !== 'number' || !Number.isFinite(time) || time < e.startTime)
    throw new Error('Invalid failure observation time.');
  return time;
}
export function stableHash(text: string, seed = 2166136261): number {
  let h = seed;
  for (let i = 0; i < text.length; i++) { h ^= text.charCodeAt(i); h = Math.imul(h, 16777619); }
  return h >>> 0;
}
export function quantile(a: number[], q: number): number {
  if (!a.length) return 0;
  const s = [...a].sort((a, b) => a - b), x = Math.min(1, Math.max(0, q)) * (s.length - 1);
  return s[Math.floor(x)] + (s[Math.ceil(x)] - s[Math.floor(x)]) * (x % 1);
}
export function clamp(x: number, lo: number, hi: number): number { return Math.max(lo, Math.min(hi, x)); }
export function formatTime(ms: number, precise = false): string {
  const m = Math.floor(Math.max(0, ms) / 60000), s = Math.floor(Math.max(0, ms) / 1000) % 60;
  return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}${precise ? '.' + String(Math.floor(ms % 1000)).padStart(3, '0') : ''}`;
}

View on GitHub (pinned to 433685b202)