affaan-m/ECC · error · ValueError

assets must be a nonempty list

Error message

assets must be a nonempty list

What it means

_assets validates the manifest's assets field and requires it to be a list with at least one element. If assets is missing, null, not a list (e.g., a dict or string), or an empty list, this error is raised from ingest_assets or validate_assets.

Solutions

  1. Add at least one asset entry under the 'assets' key of the manifest.
  2. Check for key typos — the field must be exactly 'assets'.
  3. If no assets are expected, skip calling ingest_assets/validate_assets rather than passing an empty manifest.
  4. Fix the upstream filter/generation step that produced an empty asset list.

Example fix

// before
{"assets": []}

// after
{"assets": [{"id": "hero", "modality": "image", "origin": "local", "path": "hero.png"}]}
Defensive patterns

Strategy: validation

Validate before calling

import json
data = json.load(open(path))
assets = data.get('assets')
assert isinstance(assets, list) and len(assets) > 0, 'assets must be a nonempty list'

Type guard

def has_assets(v) -> bool:
    return isinstance(v, dict) and isinstance(v.get('assets'), list) and len(v['assets']) > 0

Try / catch

try:
    ingest_assets(path)
except ValueError as e:
    if str(e) == 'assets must be a nonempty list':
        skip_ingest_or_fix_manifest(path)

Prevention

When it happens

Trigger: Manifest JSON has no 'assets' key, assets: null, assets: {}, assets: "...", or assets: [].

Common situations: Newly created template manifest with an empty assets array; key typo ("asset" instead of "assets"); a filter step that removed all assets leaving an empty list.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

        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')
        if asset.get('origin') not in tuple(ORIGINS):
            raise ValueError('Invalid asset origin')
    return value


def _provenance(asset: dict[str, Any], base: Path, verify: bool) -> dict[str, Any] | None:
    source = asset.get('provider_provenance')
    if asset['origin'] != 'external_result':

View on GitHub (pinned to 8321021c54)