Hmbown/CodeWhale · error · Error

Live pet input exceeds its tail limit.

Error message

Live pet input exceeds its tail limit.

What it means

Raised by readTail when a live pet input's tail exceeds the configured tail limit. The live tailing reader applies a bounded window on incoming text; a single append or accumulated tail larger than that limit cannot be processed incrementally, so the reader refuses the input rather than buffering unbounded data.

Solutions

  1. Pass only a bounded tail (≤256 KiB, typically the last line or last N bytes) to readTail
  2. Call petLiveTape.reset() and re-attach starting from the file's current end
  3. Track byte offsets in the driver and slice the tail per poll
  4. Pre-truncate files larger than the limit before attaching

Example fix

// before
tape.readTail(fs.readFileSync(livePath, 'utf8'));
// after
const text = fs.readFileSync(livePath, 'utf8');
const tail = text.length > 262_144 ? text.slice(-262_144).slice(text.slice(-262_144).indexOf('\n') + 1) : text;
tape.readTail(tail);
Defensive patterns

Strategy: validation

Validate before calling

function boundedTail(text: string): string {
  if (text.length <= 262_144) return text;
  const slice = text.slice(-262_144);
  const nl = slice.indexOf('\n');
  return nl === -1 ? '' : slice.slice(nl + 1);
}

Type guard

null

Try / catch

try {
  const bucket = tape.readTail(tail);
} catch (e) {
  if (e.message === 'Live pet input exceeds its tail limit.') {
    tape.reset(); // already reset internally; re-attach from file end
    tail = readLastChunk(livePath, 64 * 1024);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling readTail with a text buffer longer than 256 KiB — e.g. the driver passed the whole accumulated file instead of a bounded tail, or the file grew without the cursor advancing.

Common situations: A driver bug reading the entire JSONL file on each poll instead of the last chunk; a watcher that never fired so the tail grew unbounded; attaching to a very large pre-existing file without resetting the cursor first.

Understand the failure class

Background: "File too large" / "file size exceeds limit" errors: why libraries cap file sizes and how to fix them — this error's family across 46 libraries.

Related errors


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

Appendix: source

Thrown at pet/src/core/pet-telemetry.ts:49

}

export function decodePetJSONL(text: string): PetBucket[] {
  if (text.length > 64 * 1024 * 1024) throw new Error('Pet tape exceeds 64 MiB.');
  const rows = text.split(/\r?\n/).filter(line => line.trim()).map(line => JSON.parse(line) as unknown);
  if (rows.length > 216_000) throw new Error('Pet tape exceeds 24 hours.');
  return rows.map((row, i) => { validatePetBucket(row); if (row.sequence !== i) throw new Error('Non-contiguous pet tape.'); return row; });
}

/** A live file must advance before its contents count as a new observation.
 * Existing bytes, duplicate samples and a restarted sequence establish a
 * baseline; they never replay an old onset or human request. Drivers supply a
 * bounded tail and reset this cursor after suspension or a new attachment. */
export class PetLiveTape {
  private sequence: number | undefined;
  reset(): void { this.sequence = undefined; }
  readTail(text: string): PetBucket | undefined {
    if (!text) { this.reset(); return; }
    if (text.length > 262_144) { this.reset(); throw new Error('Live pet input exceeds its tail limit.'); }
    if (!text.endsWith('\n')) return;
    const line = text.trimEnd().split('\n').at(-1);
    if (!line) return;
    let packet: unknown;
    try { packet = JSON.parse(line); validatePetBucket(packet); }
    catch (error) { this.reset(); throw error; }
    const previous = this.sequence; this.sequence = packet.sequence;
    if (previous === undefined || packet.sequence <= previous) return;
    return packet;
  }
}

const order = (a: string, b: string) => a < b ? -1 : a > b ? 1 : 0;
const keyOf = (e: WhaleEvent) => JSON.stringify([e.traceId, e.id]);
const isContainer = (e: WhaleEvent) => e.attributes['whalesong.container'] === true
  || e.attributes['codewhale.container'] === true;

/** Compile a single trace. Unknown-duration spans provide onsets, not occupancy.

View on GitHub (pinned to 433685b202)