affaan-m/ECC · error · ContractError
at least one genre spec is required
Error message
at least one genre spec is required
What it means
validate_genre_specs requires a non-empty list of genre spec dictionaries; each spec defines a numbered style target. An empty (or missing) list cannot satisfy the 'at least one distinct genre' contract, so validation fails early with this error.
Solutions
- Provide at least one complete genre spec with number, style_fingerprint, and references
- Fix the config file so the genre list is populated
- If genres come from CLI flags/args, pass the required genre arguments explicitly
- Check that the spec-loading code isn't filtering out all entries before validation
Example fix
# before
specs = []
validate_genre_specs(specs)
# after
specs = [{"number": 1, "style_fingerprint": "noir-neon", "dry_run": True, "avoid": ["generic stock look"]}]
validate_genre_specs(specs) Defensive patterns
Strategy: validation
Validate before calling
specs = config.get("genres", [])
if not specs:
raise SystemExit('config must define at least one genre spec') Type guard
def has_genre_specs(v) -> bool:
return isinstance(v, list) and len(v) > 0 and all(isinstance(s, dict) for s in v) Try / catch
try:
validate_bundle(root)
except ContractError as e:
if 'at least one genre spec' in str(e):
print('Populate the genre list in your config before validating')
else:
raise Prevention
- Never ship template configs with empty genre lists
- Check spec-loading code doesn't filter out all entries
- Require the genre argument explicitly rather than defaulting to empty
When it happens
Trigger: Calling validate_bundle/validate_genre_specs with specs=[] or None, or a config file whose genre list was never populated.
Common situations: Template config with an empty genres key, YAML/JSON list accidentally emptied during editing, a script path where specs are read from a file that exists but has no entries, or the CLI flag for genres omitted so an empty default is used.
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
- application config exceeds local size limit
- Asset request_id/modality does not match the bundle
- bundle collections must be lists
- candidate configuration exceeds 1 MiB
- Claude scope and hook options require Claude to be selected.
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/a6d94d51971790e9.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/contract.py:120
if not stat.S_ISDIR(metadata.st_mode):
raise ContractError("output bundle root must be a directory")
pending = [root]
while pending:
directory = pending.pop()
with os.scandir(directory) as entries:
for entry in entries:
if entry.is_symlink():
raise ContractError(f"output bundle contains a symlink: {entry.path}")
if entry.is_dir(follow_symlinks=False):
pending.append(Path(entry.path))
elif not entry.is_file(follow_symlinks=False):
raise ContractError(f"output bundle contains a special file: {entry.path}")
def validate_genre_specs(specs: list[dict[str, Any]]) -> None:
"""Require complete, semantically distinct numbered genre specs."""
if not specs:
raise ContractError("at least one genre spec is required")
numbers = [spec.get("number") for spec in specs]
if len(numbers) != len(set(numbers)):
raise ContractError("genre numbers must be distinct")
fingerprints = [spec.get("style_fingerprint") for spec in specs]
signatures = [_semantic_signature(spec) for spec in specs]
if len(fingerprints) != len(set(fingerprints)) or len(signatures) != len(set(signatures)):
raise ContractError("genre references collapsed into a generic style; distinct specs required")
for spec in specs:
if spec.get("dry_run") is not True:
raise ContractError(f"genre {spec.get('number')} crosses the dry-run boundary")
measured = spec.get("measured_features")
if measured is not None:
if not isinstance(measured, dict):
raise ContractError(f"genre {spec.get('number')} has invalid measured evidence")
_validate_numeric_evidence(measured, label=f"genre {spec.get('number')} evidence")
total_duration = measured.get("total_duration")View on GitHub (pinned to 8321021c54)