affaan-m/ECC · error · ValueError

Invalid JSON at line {i}: {e}

Error message

Invalid JSON at line {i}: {e}

What it means

Raised while parse_trace() iterates JSONL lines: json.loads() failed on a single line and the underlying json.JSONDecodeError is wrapped as ValueError with the 1-based line number. The rest of the file is irrelevant — one malformed line aborts the entire parse.

Source

Thrown at skills/skill-comply/scripts/parser.py:61

    steps: tuple[Step, ...]
    threshold_promote_to_hook: float


def parse_trace(path: Path) -> list[ObservationEvent]:
    """Parse a JSONL observation trace file into sorted events."""
    if not path.is_file():
        raise FileNotFoundError(f"Trace file not found: {path}")

    text = path.read_text().strip()
    if not text:
        return []

    events: list[ObservationEvent] = []
    for i, line in enumerate(text.splitlines(), 1):
        try:
            raw = json.loads(line)
        except json.JSONDecodeError as e:
            raise ValueError(f"Invalid JSON at line {i}: {e}") from e
        try:
            events.append(ObservationEvent(
                timestamp=raw["timestamp"],
                event=raw["event"],
                tool=raw["tool"],
                session=raw["session"],
                input=raw.get("input", ""),
                output=raw.get("output", ""),
            ))
        except KeyError as e:
            raise ValueError(f"Missing required field {e} at line {i}") from e

    return sorted(events, key=lambda e: e.timestamp)


def parse_spec(path: Path) -> ComplianceSpec:
    """Parse a YAML compliance spec file."""
    if not path.is_file():

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Open the trace, jump to the reported line number, and inspect the exact bytes around it.
  2. If the corruption is from concurrent writers, gate appends with a file lock or write to per-session files then concatenate.
  3. Sanitize before parsing: strip blank lines and lines that fail a json.loads pre-check, logging them instead of aborting.
  4. Regenerate the trace from run_scenario if it cannot be repaired.

Example fix

# before
events = parse_trace(trace_path)

# after (resilient pre-filter)
import json
from pathlib import Path

text = trace_path.read_text()
for i, line in enumerate(text.splitlines(), 1):
    line = line.strip()
    if not line:
        continue
    try:
        json.loads(line)
    except json.JSONDecodeError as e:
        print(f'skipping corrupt line {i}: {e}')
        continue
events = parse_trace(trace_path)
Defensive patterns

Strategy: validation

Validate before calling

import json
from pathlib import Path

def validate_jsonl(path: Path) -> list[int]:
    bad: list[int] = []
    for i, line in enumerate(path.read_text().splitlines(), 1):
        line = line.strip()
        if not line:
            continue
        try:
            json.loads(line)
        except json.JSONDecodeError:
            bad.append(i)
    return bad

Try / catch

from scripts.parser import parse_trace
try:
    events = parse_trace(path)
except ValueError as e:
    if 'Invalid JSON' in str(e):
        # re-run after quarantining the bad line, or surface the line number
        raise
    raise

Prevention

When it happens

Trigger: A trace line that is not valid JSON: trailing comma, single quotes, unescaped control characters, a literal blank-ish line with stray whitespace, or a partially flushed write that truncated a record mid-stream.

Common situations: Two processes appending to the same JSONL with non-atomic writes; a stream-json line that was cut off by a timeout; copy-pasting trace samples from a markdown doc that introduced smart quotes.

Understand the failure class

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/f3ec30cc69471e5b. Report an issue: GitHub.