thedotmack/claude-mem · warning

Failed to parse transcript line (non-Error thrown)

Error message

Failed to parse transcript line (non-Error thrown)

What it means

Each line tailed from a transcript .jsonl file goes through JSON.parse and processor.processEntry inside one try block. The catch distinguishes real Error instances (logged at debug) from non-Error thrown values; this warning fires when something in that block threw a string, number, or plain object instead of an Error. JSON.parse itself always throws SyntaxError, so a non-Error means processEntry (or code it calls) threw a bare value.

Source

Thrown at src/services/transcripts/watcher.ts:294

  private async handleLine(
    line: string,
    watch: WatchTarget,
    schema: TranscriptSchema,
    filePath: string,
    sessionIdOverride?: string | null
  ): Promise<void> {
    try {
      const entry = JSON.parse(line);
      await this.processor.processEntry(entry, watch, schema, sessionIdOverride ?? undefined);
    } catch (error: unknown) {
      if (error instanceof Error) {
        logger.debug('TRANSCRIPT', 'Failed to parse transcript line', {
          watch: watch.name,
          file: basename(filePath)
        }, error);
      } else {
        logger.warn('TRANSCRIPT', 'Failed to parse transcript line (non-Error thrown)', {
          watch: watch.name,
          file: basename(filePath),
          error: String(error)
        });
      }
    }
  }

  private extractSessionIdFromPath(filePath: string): string | null {
    const match = filePath.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i);
    return match ? match[0] : null;
  }
}

View on GitHub (pinned to e2d1df569a)

Solutions

  1. Check the logged `error` field — String(error) shows the bare value that was thrown, which identifies the throwing module
  2. Make custom processors throw `new Error(...)` (or Error subclasses) so they land in the debug path with stack traces
  3. If the line is a partial trailing write, no action is needed — the next append re-delivers the complete line

Example fix

// before (in a custom processor)
if (!entry.type) throw 'invalid entry';

// after
if (!entry.type) throw new Error(`invalid transcript entry: missing type in ${filePath}`);
Defensive patterns

Strategy: try-catch

Validate before calling

function isCompleteJsonLine(line: string): boolean {
  const t = line.trim();
  if (!t) return false;
  try { JSON.parse(t); return true; } catch { return false; }
}

Type guard

function isErrorLike(value: unknown): value is Error {
  return value instanceof Error || (typeof value === 'object' && value !== null && 'name' in value && 'message' in value);

Try / catch

try {
  const entry = JSON.parse(line);
  await processor.processEntry(entry, watch, schema);
} catch (error: unknown) {
  const normalized = error instanceof Error ? error : new Error(String(error));
  logger.warn('TRANSCRIPT', 'Failed to parse transcript line', { watch: watch.name, file: basename(filePath) }, normalized);
}

Prevention

When it happens

Trigger: A custom or plugin-supplied processEntry path executing `throw 'invalid entry'`; a Promise rejection with a non-Error payload inside processEntry; exotic objects (e.g. from vm realms) failing `instanceof Error`.

Common situations: Tail happens to read a half-written trailing line while a session is active (usually surfaces as the debug SyntaxError path, but non-Error throws hide the real cause); third-party transcript schema processors throwing strings; DOM-exception-like objects from polyfilled environments.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20). Data as JSON: /api/errors/8644594b1af14e42. Report an issue: GitHub.