affaan-m/ECC · error · ConfigError

Invalid JSON in config: {path}

Error message

Invalid JSON in config: {path}

What it means

Raised as a ConfigError when Python's json.JSONDecodeError is caught while parsing a config file with Config.from_json(f.read()). It signals the file was opened successfully but its contents are not valid JSON. The exception is chained (`from e`) so the original decode error traceback is preserved.

Source

Thrown at skills/python-patterns/SKILL.md:153

def render_all(items: list[Renderable]) -> str:
    """Render all items that implement the Renderable protocol."""
    return "\n".join(item.render() for item in items)
```

## Error Handling Patterns

### Specific Exception Handling

```python
# Good: Catch specific exceptions
def load_config(path: str) -> Config:
    try:
        with open(path) as f:
            return Config.from_json(f.read())
    except FileNotFoundError as e:
        raise ConfigError(f"Config file not found: {path}") from e
    except json.JSONDecodeError as e:
        raise ConfigError(f"Invalid JSON in config: {path}") from e

# Bad: Bare except
def load_config(path: str) -> Config:
    try:
        with open(path) as f:
            return Config.from_json(f.read())
    except:
        return None  # Silent failure!
```

### Exception Chaining

```python
def process_data(data: str) -> Result:
    try:
        parsed = json.loads(data)
    except json.JSONDecodeError as e:
        # Chain exceptions to preserve the traceback

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Validate the file with `python -m json.tool config.json` to get the exact line/column of the syntax error.
  2. Check for common JSON-invalid syntax: comments (// or /* */), trailing commas, single-quoted strings, unquoted keys.
  3. Re-encode the file as plain UTF-8 without BOM (e.g. `sed -i '1s/^\xEF\xBB\xBF//' config.json`).
  4. If you must allow comments/relaxed JSON, parse with json5.loads or strip comments before json.loads, but document that the config schema is no longer strict JSON.

Example fix

// before
{}

# after: validate and report the exact offset
import json
try:
    with open(path) as f:
        return Config.from_json(f.read())
except json.JSONDecodeError as e:
    raise ConfigError(f"Invalid JSON in {path} at line {e.lineno} col {e.colno}: {e.msg}") from e
Defensive patterns

Strategy: validation

Validate before calling

import json, pathlib
def is_valid_json_file(path):
    p = pathlib.Path(path)
    if not p.exists():
        return False, "file missing"
    try:
        json.loads(p.read_text(encoding="utf-8"))
        return True, None
    except json.JSONDecodeError as e:
        return False, f"line {e.lineno} col {e.colno}: {e.msg}"

ok, err = is_valid_json_file(path)
if not ok:
    raise SystemExit(f"refusing to load bad config: {err}")

Type guard

def is_json_decodable(s: str) -> bool:
    try:
        json.loads(s)
        return True
    except (json.JSONDecodeError, TypeError):
        return False

Try / catch

try:
    cfg = load_config(path)
except ConfigError as e:
    log.error("config load failed: %s", e)
    raise SystemExit(2)
except FileNotFoundError as e:
    log.error("config file missing: %s", e)
    raise SystemExit(2)

Prevention

When it happens

Trigger: Calling load_config(path) where the file at `path` exists but contains malformed JSON (trailing commas, single quotes, unquoted keys, BOM-prefixed bytes, comments, or a file truncated mid-write). Config.from_json() internally calls json.loads() which raises json.JSONDecodeError.

Common situations: Hand-edited JSON config files with comments or trailing commas; a config written by another tool that emitted JSON5 or JSONC; a config file partially written and read during a crash; UTF-8 with BOM from a Windows editor.

Understand the failure class

Related errors


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