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
- Validate the file locally: python -c "import json;json.load(open('manifest.json'))" to see the exact parse error.
- Fix JSON syntax (remove comments/trailing commas) or convert the file to UTF-8 without BOM.
- Confirm the file is non-empty and readable by the current user.
- 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
- Validate manifests with a JSON linter in CI
- Save manifests as UTF-8 without BOM
- Generate manifests with json.dump instead of hand-editing
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
- Expected a JSON object
- Failed to read
- gh returned invalid JSON
- gh returned invalid JSON
- Malformed coordination JSON in body
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)