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
- Open the trace, jump to the reported line number, and inspect the exact bytes around it.
- If the corruption is from concurrent writers, gate appends with a file lock or write to per-session files then concatenate.
- Sanitize before parsing: strip blank lines and lines that fail a json.loads pre-check, logging them instead of aborting.
- 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
- Use a single writer per JSONL file, or gate appends with a file lock to avoid interleaved writes.
- Add a jsonschema check over the first record of every trace before full parsing.
- In stream-json adapters, never write a partial line on timeout — buffer until the record is complete.
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Trace file not found: {path}
- Missing required field {e} at line {i}
- ECC_PROJECT_DIR must be a child path within /workspace.
- Unknown argument: ${arg}
- --json may only be provided once
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/f3ec30cc69471e5b.
Report an issue: GitHub.