Hmbown/CodeWhale · error · Error
Invalid JSON on nonempty line
Error message
Invalid JSON on nonempty line ${i + 1}. Import cancelled; no rows were skipped. What it means
When the whole input is not a single valid JSON document, importTrace falls back to line-delimited JSON (JSONL). Each nonempty line must parse; the first line that fails aborts the entire import with this error naming the 1-based line number, guaranteeing no rows are skipped silently.
Solutions
- Open the file at the reported line number and fix or remove the malformed line.
- Verify the file is valid JSON (single document) or strict JSONL (one JSON object per line); re-export if truncated.
- Validate each line with JSON.parse before calling importTrace to locate all bad lines at once.
- If the source is pretty-printed JSON, do not split it into lines yourself — pass it whole.
Example fix
// before
const text = lines.join(''); // concatenated objects, invalid JSONL
importTrace(text);
// after
const text = lines.filter(l => l.trim()).map(l => JSON.stringify(JSON.parse(l))).join('\n');
importTrace(text); Defensive patterns
Strategy: validation
Validate before calling
function validateJsonOrJsonl(text: string): void {
try { JSON.parse(text); return; } catch {}
text.split(/\r?\n/).filter(l => l.trim()).forEach((l, i) => {
try { JSON.parse(l); } catch { throw new Error(`Invalid JSON on line ${i + 1}`); }
});
} Type guard
function isJsonLine(line: string): boolean {
try { JSON.parse(line); return true; } catch { return false; }
} Try / catch
try {
importTrace(text);
} catch (e) {
const m = /Invalid JSON on nonempty line (\d+)/.exec(e.message);
if (m) console.error(`Malformed JSONL at line ${m[1]}; fix or drop that line.`);
} Prevention
- Keep exports as strict JSON (one document) or strict JSONL (one object per line).
- Never split pretty-printed JSON into lines yourself.
- Guard against truncated downloads: compare byte size or checksum with the exporter's report.
- Pre-validate with a JSONL linter before importing.
When it happens
Trigger: importTrace with mixed/truncated JSONL (a partially written last line), plain text or CSV passed as JSON, pretty-printed JSON whose lines are individually invalid (it fails JSON.parse then each line fails), or concatenated JSON objects without newline separation.
Common situations: File truncated by a crash or incomplete download; wrong file selected (a log or CSV instead of JSONL); editing a JSONL file by hand; export written without a trailing flush.
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
- Cargo metadata is not valid JSON
- Codewhale stream-json line
- Codewhale stream-json line
- Failed to parse Ollama /api/tags JSON
- InvalidData
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/aa18f9820d29df07.
Report an issue: GitHub.
Appendix: source
Thrown at pet/src/core/ingest.ts:231
}
return { events, origins: new Map([...bases].map(([k, v]) => [k, v.toString()])), warnings };
}
/** Parse strictly: malformed lines or duplicate identities never disappear silently. */
export function importTrace(text: string, filename = 'Imported trace', options: ImportOptions = {}): Trace[] {
const mode = options.privacy ?? 'redact', maxEvents = options.maxEvents ?? 250_000;
if (!['redact', 'metadata', 'retain'].includes(mode)) throw new Error('Unknown privacy mode.');
const maxTraces = options.maxTraces ?? 8;
if (!Number.isInteger(maxEvents) || maxEvents < 1 || maxEvents > 250_000) throw new Error('maxEvents must be in [1, 250000].');
if (!Number.isInteger(maxTraces) || maxTraces < 1 || maxTraces > 64) throw new Error('maxTraces must be in [1, 64].');
if (new TextEncoder().encode(text).length > (options.maxBytes ?? 64 * 1024 * 1024)) throw new Error('File exceeds the 64 MiB MVP import limit. Split the export by trace.');
const trimmed = text.replace(/^\uFEFF/, '').trim();
if (!trimmed) throw new Error('The trace file is empty.');
let document: unknown;
try { document = JSON.parse(trimmed); }
catch {
document = trimmed.split(/\r?\n/).filter(l => l.trim()).map((line, i) => {
try { return JSON.parse(line); } catch { throw new Error(`Invalid JSON on nonempty line ${i + 1}. Import cancelled; no rows were skipped.`); }
});
}
// Transform before both normalization and raw retention, so raw cannot bypass redaction.
const safe = mode === 'retain' ? (options.redactor ? options.redactor(document, '') : document) : redact(document, '', options.redactor);
const root = obj(safe);
if(root.format === 'whalesong.evidence/v1') return [evidenceToTrace(validateBundle(root, Math.min(maxEvents, 100_000)))];
if (isCodewhaleSession(safe)) {
const trace = fromCodewhaleSession(safe, filename, maxEvents);
trace.privacy = mode;
trace.events = trace.events.map(e => privacyEvent(e, mode));
return [trace];
}
const records = Array.isArray(safe) ? safe : Array.isArray(root.events) ? root.events : null;
if (isCodewhaleRuntimeDocument(records ?? [safe])) {
const trace = fromCodewhaleRuntime(records ?? [safe], filename, maxEvents);
trace.privacy = mode;
trace.events = trace.events.map(e => privacyEvent(e, mode));
return [trace];View on GitHub (pinned to 433685b202)