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

  1. Quote the name so YAML keeps it a string: `name: "2024-report"`.
  2. Use a word-first slug name to avoid YAML number coercion entirely.
  3. 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

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


AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14). Data as JSON: /api/errors/bdec75343f298e46. Report an issue: GitHub.