github/spec-kit · error · ValidationError
Invalid requires: expected a mapping, got {type(requires).__
Error message
Invalid requires: expected a mapping, got {type(requires).__name__} What it means
The top-level requires section of extension.yml must be a mapping (YAML dict). The validator indexes self.data["requires"] and checks isinstance(requires, dict); a list, string, or scalar fails immediately.
Source
Thrown at src/specify_cli/extensions/__init__.py:346
# Validate optional category field (free-form string)
if "category" in ext:
if not isinstance(ext["category"], str) or not ext["category"].strip():
raise ValidationError(
"Invalid extension.category: must be a non-empty string"
)
# Validate optional effect field
if "effect" in ext:
if not isinstance(ext["effect"], str) or ext["effect"] not in VALID_EFFECTS:
raise ValidationError(
f"Invalid extension.effect '{ext.get('effect')}': "
f"must be one of {sorted(VALID_EFFECTS)}"
)
# Validate requires section
requires = self.data["requires"]
if not isinstance(requires, dict):
raise ValidationError(
f"Invalid requires: expected a mapping, got {type(requires).__name__}"
)
if "speckit_version" not in requires:
raise ValidationError("Missing requires.speckit_version")
# Presence alone is not enough: check_compatibility() feeds this value to
# ``SpecifierSet(required)``, guarded only by ``except InvalidSpecifier``,
# which a non-string escapes two different ways. A float/int/bool/None
# raises TypeError from the constructor, while a list or dict is an
# *iterable*, so SpecifierSet accepts it and the failure surfaces much
# later as ``AttributeError: 'str' object has no attribute 'filter'`` from
# inside .contains(). Neither is a CompatibilityError, so both bypass the
# CLI's "Compatibility Error" handler and exit 1 with a raw traceback
# naming no field. An unquoted ``speckit_version: 1.0`` is an easy YAML
# slip. Mirrors the sibling IntegrationDescriptor, which already requires
# a non-empty string here.
if (
not isinstance(requires["speckit_version"], str)
or not requires["speckit_version"].strip()View on GitHub (pinned to bf88c9f9a8)
Solutions
- Rewrite requires as a mapping: `requires:\n speckit_version: ">=1.0.0"`.
- Remove stray list dashes or quoting that turns the section into a string.
- Model it on a known-good bundled extension manifest (e.g. extensions/agent-context).
Example fix
# before requires: ">=1.0.0" # after requires: speckit_version: ">=1.0.0"
Defensive patterns
Strategy: validation
Validate before calling
def requires_ok(data: dict) -> bool:
return isinstance(data.get("requires"), dict) Type guard
def is_requires_mapping(data: dict) -> bool:
return isinstance(data.get("requires"), dict) Prevention
- requires is a mapping, never a list or a bare constraint string.
- Keep a known-good manifest next to your authoring docs and diff against it.
When it happens
Trigger: A manifest has `requires: "spec-kit>=1.0"` or `requires: - speckit_version: '>=1.0'` (list of mappings). The isinstance check fails and ValidationError names the actual type.
Common situations: Author assumes requires is a list of requirement strings (pip-style) or a single constraint string, instead of a mapping keyed by speckit_version.
Related errors
- Invalid provides: expected a mapping, got {type(provides).__
- Invalid provides.commands: expected a list
- Invalid provides.templates: expected a list
- Invalid provides.scripts: expected a list
- Invalid hooks: expected a mapping
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/29d28c12babb3d2e.
Report an issue: GitHub.