github/spec-kit · error · ValidationError
Each command entry in 'provides.commands' must be a mapping
Error message
Each command entry in 'provides.commands' must be a mapping
What it means
Every command mapping in provides.commands must contain both name and file keys. name is the slash-command identifier agents invoke; file is the template file installed for it. Either key missing aborts validation of the manifest.
Source
Thrown at src/specify_cli/extensions/__init__.py:445
)
if "priority" in entry:
priority = entry["priority"]
if not isinstance(priority, int) or isinstance(priority, bool):
raise ValidationError(
f"Hook '{hook_name}' has invalid 'priority': "
"must be an integer"
)
if priority < 1:
raise ValidationError(
f"Hook '{hook_name}' has invalid 'priority': "
"must be >= 1"
)
# Validate commands; track renames so hook references can be rewritten.
rename_map: Dict[str, str] = {}
for cmd in commands:
if not isinstance(cmd, dict):
raise ValidationError(
"Each command entry in 'provides.commands' must be a mapping"
)
if "name" not in cmd or "file" not in cmd:
raise ValidationError("Command missing 'name' or 'file'")
# The pattern match below would raise a bare TypeError on a
# non-string name (``name: 2``), escaping the ValidationError
# contract. The 'file' field needs no check here:
# relative_extension_path_violation() below already rejects a
# non-string value.
if not isinstance(cmd["name"], str):
raise ValidationError(
f"Invalid command name: expected a string, "
f"got {type(cmd['name']).__name__}"
)
# Validate the 'file' field at manifest-load time using the single
# shared policy in relative_extension_path_violation(), so manifest
# validation cannot drift from the runtime registrar guard. This isView on GitHub (pinned to bf88c9f9a8)
Solutions
- Add both keys: `- name: check\n file: commands/check.md`.
- Point file at an existing file inside the extension directory.
- Verify key spelling — exact literals `name` and `file` are required.
Example fix
# before
provides:
commands:
- name: check
# after
provides:
commands:
- name: check
file: commands/check.md Defensive patterns
Strategy: validation
Validate before calling
def commands_have_name_and_file(data: dict) -> bool:
cmds = (data.get("provides") or {}).get("commands") or []
return all("name" in c and "file" in c for c in cmds if isinstance(c, dict)) Type guard
def is_complete_command_entry(v: object) -> bool:
return isinstance(v, dict) and isinstance(v.get("name"), str) and "file" in v Prevention
- Both `name` and `file` are mandatory per command entry.
- Point file at a real file in the extension directory; the path is also validated for containment.
When it happens
Trigger: A command entry `{name: check}` with no file, or `{file: commands/check.md}` with no name. The `"name" not in cmd or "file" not in cmd` check fires.
Common situations: Author omits file assuming the loader infers it from name, or drops name for a file-only entry. Typos (filename, cmd) also trigger it.
Related errors
- Missing requires.speckit_version
- Extension must provide at least one command, hook, or event
- Hook '{hook_name}' missing required 'command' field
- Hook '{hook_name}' has invalid 'priority': must be >= 1
- Command missing 'name' or 'file'
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/009c3f778c4128e0.
Report an issue: GitHub.