affaan-m/ECC · error · ValueError
bundle collections must be lists
Error message
bundle collections must be lists
What it means
After deep-copying the config, build_application_bundle normalizes the collections audio, candidates, inserts, and historical_receipts and requires each to be a JSON list. Non-list values (dict, string, null supplied explicitly) break the deterministic per-item processing loops, so the error is raised before any bundle work begins.
Solutions
- Wrap single items in a list: candidates: [item] instead of candidates: item
- Convert keyed maps to lists of objects with the key moved into an 'id' field
- Replace explicit null with a missing key (setdefault will supply []) or an empty list []
- Validate types before calling: all(isinstance(cfg[k], list) for k in ('audio','candidates','inserts','historical_receipts'))
Example fix
// before
"candidates": {"c1": {...}}
// after
"candidates": [{"id": "c1", ...}] Defensive patterns
Strategy: type-guard
Validate before calling
COLLECTIONS = ("audio", "candidates", "inserts", "historical_receipts")
assert all(isinstance(cfg.get(k, []), list) for k in COLLECTIONS) Type guard
def collections_are_lists(cfg):
return all(isinstance(cfg.get(k, []), list) for k in ("audio","candidates","inserts","historical_receipts")) Try / catch
try:
bundle = build_application_bundle(cfg, ci)
except ValueError as e:
if "collections must be lists" in str(e):
for k in ("audio","candidates","inserts","historical_receipts"):
if not isinstance(cfg.get(k, []), list):
cfg[k] = list(cfg[k].values()) if isinstance(cfg[k], dict) else []
bundle = build_application_bundle(cfg, ci)
else:
raise Prevention
- Serialize collections as JSON arrays, never maps
- Coerce null collections to [] in your config loader
- Schema-validate configs (e.g. with a JSON Schema) before bundling
- Wrap single items into one-element lists at authoring time
When it happens
Trigger: Passing config with any of cfg['audio'], cfg['candidates'], cfg['inserts'], or cfg['historical_receipts'] set to a non-list (e.g. a dict keyed by id, a single object instead of a one-element list, or null explicitly present since setdefault only fills missing keys).
Common situations: Configs migrated from a schema that used a map/dict for candidates; YAML/JSON hand-edits collapsing a list to a single object; a serialization layer turning empty lists into null.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- A normalized install request is required
- application config exceeds local size limit
- Asset request_id/modality does not match the bundle
- at least one genre spec is required
- candidate configuration exceeds 1 MiB
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/7977d9d648374a76.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/integration.py:358
if type(local_only) is not bool:
raise ValueError("local_only must be an exact boolean")
_object(config, _REQUIRED, _OPTIONAL)
if len(_canonical(config)) > _MAX_JSON:
raise ValueError("application config exceeds local size limit")
if local_only:
if compiled_input is not None:
raise ValueError("local-only preservation cannot accept provider input")
else:
_object(compiled_input, {"source_video", "compiled_prompt"})
url = urlsplit(_text(compiled_input["source_video"]))
if url.scheme != "https" or not url.hostname or url.username or url.password:
raise ValueError("source reference must be HTTPS without embedded credentials")
_text(compiled_input["compiled_prompt"])
cfg = copy.deepcopy(config)
for key in ("audio", "candidates", "inserts", "historical_receipts"):
cfg.setdefault(key, [])
if not isinstance(cfg[key], list):
raise ValueError("bundle collections must be lists")
if local_only and (cfg["candidates"] or cfg["inserts"]):
raise ValueError("local-only preservation cannot contain candidates or inserts")
tracks = _snapshot(cfg["baseline"])
_binding(cfg["source"], tracks, cfg["baseline"])
audio_keys = [_binding(item, tracks, cfg["baseline"], audio=True) for item in cfg["audio"]]
expected_audio = {(t, i) for t, clips in tracks.items() if t.startswith("audio")
for i in range(len(clips))}
if len(set(audio_keys)) != len(audio_keys) or set(audio_keys) != expected_audio:
raise ValueError("every original audio clip must be preserved exactly once")
stack = _preserved_stack(cfg["protected_intervals"], tracks, cfg["baseline"]["timeline_range"])
input_hash = None if local_only else _digest(compiled_input)
edit_hash = _digest({key: cfg[key] for key in _REQUIRED})
if not local_only:
candidates = _candidates(cfg["candidates"], cfg["source"]["media"]["sha256"],
input_hash, compiled_input["source_video"])
_inserts(cfg["inserts"], candidates, cfg, input_hash, edit_hash)
for receipt in cfg["historical_receipts"]:
_artifact(receipt)View on GitHub (pinned to 8321021c54)