github/spec-kit · error · ValidationError
Hook '{hook_name}' has invalid 'priority': must be >= 1
Error message
Hook '{hook_name}' has invalid 'priority': must be >= 1 What it means
Every element of provides.commands must be a mapping (dict) describing one command (with name and file keys). A non-dict element — string, number, list — is rejected before any key checks run.
Source
Thrown at src/specify_cli/extensions/__init__.py:436
for entry in coerce_hook_entries(hook_config):
if not isinstance(entry, dict):
raise ValidationError(
f"Invalid hook '{hook_name}': "
"expected a mapping or list of mappings"
)
if not entry.get("command"):
raise ValidationError(
f"Hook '{hook_name}' missing required 'command' field"
)
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.View on GitHub (pinned to bf88c9f9a8)
Solutions
- Make each entry a mapping: `- name: check\n file: commands/check.md`.
- Provide both name and file for every command.
- Avoid shorthand — the schema has no string-only command form.
Example fix
# before
provides:
commands:
- check
# after
provides:
commands:
- name: check
file: commands/check.md Defensive patterns
Strategy: type-guard
Validate before calling
def commands_are_mappings(data: dict) -> bool:
cmds = (data.get("provides") or {}).get("commands") or []
return all(isinstance(c, dict) for c in cmds) Type guard
def is_command_entry(v: object) -> bool:
return isinstance(v, dict) and "name" in v and "file" in v Prevention
- No string shorthand for commands — every entry needs name and file keys.
- Write entries as `- name: x` / `file: y` pairs from a snippet template.
When it happens
Trigger: `provides:\n commands:\n - check` — a bare string element instead of a mapping. isinstance(cmd, dict) fails on the first loop iteration.
Common situations: Author writes a shorthand list of command names assuming the file is derived, or a malformed list-of-lists from a bad merge.
Related errors
- Each command entry in 'provides.commands' must be a mapping
- Command missing 'name' or 'file'
- Output path {candidate!r} escapes directory {base!r}
- Invalid command name {cmd_name!r}: {name_reason}
- Aliases for command {cmd_name!r} must be a list
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/74c02ad0bf57509e.
Report an issue: GitHub.