github/spec-kit · error · ValidationError
Invalid provides: expected a mapping, got {type(provides).__
Error message
Invalid provides: expected a mapping, got {type(provides).__name__} What it means
The top-level provides section must be a mapping. provides declares what the extension contributes (commands, templates, scripts); the validator requires it to be a dict before pulling the sub-keys.
Source
Thrown at src/specify_cli/extensions/__init__.py:374
# 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()
):
raise ValidationError(
"Invalid requires.speckit_version: expected a non-empty string, "
f"got {type(requires['speckit_version']).__name__}"
)
# Validate provides section
provides = self.data["provides"]
if not isinstance(provides, dict):
raise ValidationError(
f"Invalid provides: expected a mapping, got {type(provides).__name__}"
)
commands = provides.get("commands", [])
templates = provides.get("templates", [])
scripts = provides.get("scripts", [])
hooks = self.data.get("hooks")
events = self.data.get("events")
if "commands" in provides and not isinstance(commands, list):
raise ValidationError("Invalid provides.commands: expected a list")
if "templates" in provides and not isinstance(templates, list):
raise ValidationError("Invalid provides.templates: expected a list")
if "scripts" in provides and not isinstance(scripts, list):
raise ValidationError("Invalid provides.scripts: expected a list")
if "hooks" in self.data and not isinstance(hooks, dict):
raise ValidationError("Invalid hooks: expected a mapping")
if "events" in self.data:
from ..events import validate_eventsView on GitHub (pinned to bf88c9f9a8)
Solutions
- Structure provides as a mapping with list-valued sub-keys: `provides:\n commands:\n - name: ...`.
- Fix indentation so commands/templates/scripts are nested under provides, not siblings at the wrong level.
- Compare against a bundled extension's manifest for the exact shape.
Example fix
# before
provides:
- name: check
file: commands/check.md
# after
provides:
commands:
- name: check
file: commands/check.md Defensive patterns
Strategy: validation
Validate before calling
def provides_ok(data: dict) -> bool:
return isinstance(data.get("provides"), dict) Type guard
def is_provides_mapping(data: dict) -> bool:
return isinstance(data.get("provides"), dict) Prevention
- provides is a mapping whose values (commands/templates/scripts) are lists.
- Watch indentation: one wrong level turns the section into a list or scalar.
When it happens
Trigger: A manifest has `provides:` followed by a list of command entries, or `provides: commands` as a bare string. isinstance(provides, dict) fails and the error names the actual type.
Common situations: Author flattens the structure — putting the commands list directly under provides instead of under provides.commands — or the YAML indentation collapses provides into a scalar.
Related errors
- Invalid requires: expected a mapping, got {type(requires).__
- 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/db7043a1680d73e8.
Report an issue: GitHub.