affaan-m/ECC · error · ValueError
input_artifacts must be a list of local paths
Error message
input_artifacts must be a list of local paths
What it means
ingest_assets validates the optional 'input_artifacts' key of the ingest config: it must be a list (of local path strings, each later fingerprinted). A non-list value (string, dict, null, number) is rejected before receipt construction.
Solutions
- Wrap the value in a list: input_artifacts must be an array of path strings.
- If there are no input artifacts, omit the key entirely (it defaults to []).
- Validate the config shape before calling ingest_assets.
Example fix
# before
{"input_artifacts": "refs/style.png"}
# after
{"input_artifacts": ["refs/style.png"]} Defensive patterns
Strategy: validation
Validate before calling
cfg = json.loads(open('config.json').read())
inputs = cfg.get('input_artifacts', [])
assert isinstance(inputs, list) and all(isinstance(p, str) for p in inputs) Type guard
def valid_inputs(cfg) -> bool:
v = cfg.get('input_artifacts', [])
return isinstance(v, list) and all(isinstance(p, str) for p in v) Prevention
- Always write artifact lists as JSON arrays, even for single items.
- Omit input_artifacts rather than setting it to null.
- Schema-validate configs (e.g. jsonschema) before ingest.
When it happens
Trigger: Passing config with input_artifacts set to a single string path instead of a list, a comma-joined string, null, or an object mapping names to paths; YAML/JSON configs where a single-entry list was written without brackets.
Common situations: Hand-written configs: input_artifacts: "data/ref.png" instead of ["data/ref.png"]; config generators emitting an object for named artifacts; null when the key is present but no artifacts exist (omit the key instead).
Related errors
- candidate configuration exceeds 1 MiB
- Invalid at : expected a JSON object
- application config exceeds local size limit
- application request must be a JSON object
- Asset request_id/modality does not match the bundle
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/e0fbe0ccc1a0764b.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/assets.py:201
config = _load(config_file)
base = config_file.parent
output = _path(out_receipt, Path.cwd())
if output.exists():
raise ValueError(f'Receipt output already exists: {output}')
binding, requests = (_bundle(config['bundle_dir'], base, False)
if 'bundle_dir' in config else (None, {}))
records = []
for asset in _assets(config.get('assets')):
record = {key: asset[key] for key in ('id', 'modality', 'origin')}
record.update(_taste(asset, requests, False))
record.update(_fingerprint(_path(asset.get('path'), base), asset['modality']))
provenance = _provenance(asset, base, False)
if provenance is not None:
record['provider_provenance'] = provenance
records.append(record)
inputs = config.get('input_artifacts', [])
if not isinstance(inputs, list):
raise ValueError('input_artifacts must be a list of local paths')
receipt = dict(schema=SCHEMA, provider_calls=0, provider_execution=False,
assets=records, input_artifacts=[_fingerprint(_path(p, base)) for p in inputs])
if binding is not None:
receipt['bundle_receipt'] = binding
if 'genre_spec' in config:
receipt['genre_spec'] = _fingerprint(_path(config['genre_spec'], base))
# All inputs already exist, so the output-exists gate also prevents collisions.
try:
with output.open('x', encoding='utf-8') as stream:
json.dump(receipt, stream, indent=2, sort_keys=True)
stream.write('\n')
except OSError as exc:
raise ValueError(f'Cannot create exclusive receipt: {output}') from exc
return receipt
def validate_assets(receipt_path: str | Path) -> dict[str, Any]:
"""Re-hash every bound file and validate receipt semantics; no remote calls."""View on GitHub (pinned to 8321021c54)