github/spec-kit · error · ValidationError
Command missing 'name' or 'file'
Error message
Command missing 'name' or 'file'
What it means
The command name in provides.commands must be a string. The source comment explains the guard exists because the pattern match used further down would raise a bare TypeError on a non-string name (`name: 2`), escaping the ValidationError contract; the file field needs no such check since relative_extension_path_violation() already rejects non-strings.
Source
Thrown at src/specify_cli/extensions/__init__.py:449
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 is
# defense-in-depth: the command/skill/preset readers also contain
# the resolved path, but rejecting an unsafe value here surfaces a
# clear error instead of silently skipping the command.
cmd_file = cmd["file"]View on GitHub (pinned to bf88c9f9a8)
Solutions
- Quote the name so YAML keeps it a string: `name: "2024-report"`.
- Use a word-first slug name to avoid YAML number coercion entirely.
- Check the reported type name in the error — int/float/list each point at a missing quote.
Example fix
# before (YAML parses 2024-report as int for `2024-report`? no — but `2024` alone is) - name: 2024 # after - name: "2024-report"
Defensive patterns
Strategy: type-guard
Validate before calling
def command_names_are_strings(data: dict) -> bool:
cmds = (data.get("provides") or {}).get("commands") or []
return all(isinstance(c.get("name"), str) for c in cmds if isinstance(c, dict)) Type guard
def is_string_command_name(v: object) -> bool:
return isinstance(v, str) Prevention
- Quote command names that start with digits so YAML keeps them strings.
- This guard exists because the downstream regex match would raise a bare TypeError — so always ship string names.
When it happens
Trigger: `name: 2` or `name: [check]` in a command entry — YAML coerces unquoted values to int/list. The isinstance(cmd["name"], str) check fails and names the actual type.
Common situations: Numeric command names (e.g. `name: 2024-report` quoted incorrectly so YAML sees `2024` as int), or names written as YAML flow lists.
Related errors
- Invalid requires.speckit_version: expected a non-empty strin
- Hook '{hook_name}' has invalid 'priority': must be an intege
- Hook '{hook_name}' has invalid 'priority': must be >= 1
- Each command entry in 'provides.commands' must be a mapping
- Output path {candidate!r} escapes directory {base!r}
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/bdec75343f298e46.
Report an issue: GitHub.