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
- Check the logged `error` field — String(error) shows the bare value that was thrown, which identifies the throwing module
- Make custom processors throw `new Error(...)` (or Error subclasses) so they land in the debug path with stack traces
- 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
- Always throw Error instances from processors so stack traces survive the catch
- Skip empty and visibly truncated trailing lines before parsing
- Log the raw non-Error value (String(error)) — it is the only clue to the throwing module
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.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/8644594b1af14e42.
Report an issue: GitHub.