affaan-m/ECC · error · ValueError
modality must be image, video or 3d_asset
Error message
modality must be image, video or 3d_asset
What it means
Asset ingestion/validation requires each asset to declare a modality from the fixed set image, video, 3d_asset. The library throws this ValueError when `asset.get('modality')` is missing or not one of those exact strings, because downstream processing (bundle lookup, fingerprinting) dispatches on modality.
Solutions
- Set the asset's 'modality' field to exactly one of 'image', 'video', or '3d_asset'
- Check for typos/case differences in the manifest and fix them
- Print the offending asset dict (the error doesn't name it) to see the actual modality value
- If migrating from an older manifest format, rename legacy modality terms to the current tuple
Example fix
// before
{"id": "a1", "modality": "3d", "origin": "internal"}
// after
{"id": "a1", "modality": "3d_asset", "origin": "internal"} Defensive patterns
Strategy: validation
Validate before calling
ALLOWED = {"image", "video", "3d_asset"}
for a in assets:
if a.get("modality") not in ALLOWED:
raise ValueError(f"asset {a.get('id')}: modality must be one of {sorted(ALLOWED)}, got {a.get('modality')!r}") Type guard
def is_valid_modality(m) -> bool:
return m in ("image", "video", "3d_asset") Try / catch
try:
ingest_assets(assets)
except ValueError as e:
if "modality must be" in str(e):
log.error("asset with bad modality: %s", find_offending_asset(assets)); fix_and_retry()
else:
raise Prevention
- Define a single MODALITIES constant/enum in your pipeline and always construct assets through it
- Add a schema check (e.g. zod/pydantic/JSON Schema) on manifests before calling the library
- Lint manifests for exact-string enum fields in CI
When it happens
Trigger: Calling ingest_assets or validate_assets with an asset dict lacking a 'modality' key, or with a value like 'image2', 'Image' (case mismatch), 'mesh', or None.
Common situations: Typos in hand-written asset manifests; older configs using a pre-rename modality name; programmatically generated assets that omit modality; YAML/JSON key casing mistakes ('Modality').
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- Invalid asset origin
- Provider provenance requires external_result origin
- -32602
- a claim token is required
- a confirmed nonempty coordinate is required
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/9b9f8de442f9504a.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/assets.py:100
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':
if source is not None:
raise ValueError('Provider provenance requires external_result origin')
return None
if not isinstance(source, dict):
raise ValueError('external_result requires provider provenance and local evidence')
provider = _text(source.get('provider'), 'provider')
identifiers = {key: _text(source[key], key) for key in ('request_id', 'workflow_id')
if key in source}
if not identifiers:
raise ValueError('Provider provenance requires request_id or workflow_id')View on GitHub (pinned to 8321021c54)