affaan-m/ECC · error · ValueError

Cannot read JSON object

Error message

Cannot read JSON object: {path}

What it means

_load fingerprints the path and then parses it with json.loads(path.read_text(encoding='utf-8')). Any JSONDecodeError (malformed JSON) or OSError (unreadable/missing file) is re-raised as ValueError('Cannot read JSON object: {path}'). Note the fingerprint inside _load can also fail first with its own errors.

Solutions

  1. Validate the file locally: python -c "import json;json.load(open('manifest.json'))" to see the exact parse error.
  2. Fix JSON syntax (remove comments/trailing commas) or convert the file to UTF-8 without BOM.
  3. Confirm the file is non-empty and readable by the current user.
  4. Re-generate the manifest from its producing tool instead of hand-editing.

Example fix

// before (manifest.json)
{"assets": [{"id": "a",}]}  // trailing comma

// after
{"assets": [{"id": "a"}]}
Defensive patterns

Strategy: validation

Validate before calling

import json
with open(path, encoding='utf-8') as f:
    json.load(f)  # raises json.JSONDecodeError with position info first

Try / catch

try:
    data = validate_assets(path)
except ValueError as e:
    if str(e).startswith('Cannot read JSON object'):
        show_json_lint_output(path)

Prevention

When it happens

Trigger: Calling _load (directly or via _bundle, ingest_assets, validate_assets) on a manifest file containing invalid JSON (trailing commas, comments, BOM issues, non-UTF8 encoding) or a path that cannot be read.

Common situations: Hand-edited manifest with a syntax error; file saved in Latin-1 or UTF-16; file truncated by a failed write; manifest contains JSONC-style comments; empty file.

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.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/4cfe7bc096fa3d71. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/assets.py:82

    def identity(info: os.stat_result) -> tuple[int, ...]:
        return (info.st_dev, info.st_ino, info.st_size, info.st_mtime_ns, info.st_ctime_ns)
    if len({identity(info) for info in (before, opened, after, final)}) != 1:
        raise ValueError(f'File changed while hashing: {path}')
    if modality == '3d_asset':
        if len(header) != 12:
            raise ValueError(f'Truncated GLB header: {path}')
        magic, version, size = struct.unpack('<4sII', header)
        if magic != b'glTF' or version != 2 or size != final.st_size:
            raise ValueError(f'Invalid GLB magic, version or declared size: {path}')
    return dict(path=str(path), bytes=final.st_size, sha256=digest.hexdigest())


def _load(path: Path) -> dict[str, Any]:
    _fingerprint(path)
    try:
        data = json.loads(path.read_text(encoding='utf-8'))
    except (ValueError, OSError) as exc:
        raise ValueError(f'Cannot read JSON object: {path}') from exc
    if not isinstance(data, dict):
        raise ValueError('Expected a JSON object')
    return data


def _assets(value: Any) -> list[dict[str, Any]]:
    if not isinstance(value, list) or not value:
        raise ValueError('assets must be a nonempty list')
    ids = set()
    for asset in value:
        if not isinstance(asset, dict):
            raise ValueError('Each asset must be an object')
        asset_id = _text(asset.get('id'), 'asset id')
        if asset_id in ids:
            raise ValueError(f'Duplicate asset id: {asset_id}')
        ids.add(asset_id)
        if asset.get('modality') not in tuple(MODALITIES):
            raise ValueError('modality must be image, video or 3d_asset')

View on GitHub (pinned to 8321021c54)