github/spec-kit · error · ValidationError

Invalid command name: expected a string, got {type(cmd['name

Error message

Invalid command name: expected a string, got {type(cmd['name']).__name__}

What it means

Raised while validating an extension manifest's 'provides.commands' section: a command entry declared a 'name' key whose value is not a Python string (e.g. an int or bool). The explicit isinstance check exists because the later regex match (EXTENSION_COMMAND_NAME_PATTERN.match) would raise a bare TypeError on a non-string, escaping the ValidationError contract the loader relies on. It is a manifest-authoring error, caught at extension load/install time.

Source

Thrown at src/specify_cli/extensions/__init__.py:456

                                "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"]
            reason = relative_extension_path_violation(cmd_file)
            if reason:
                label = repr(cmd_file) if isinstance(cmd_file, str) else f"for command '{cmd.get('name')}'"
                raise ValidationError(f"Invalid command 'file' {label}: {reason}")

            # Validate command name format
            if not EXTENSION_COMMAND_NAME_PATTERN.match(cmd["name"]):

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Open the extension manifest and make every provides.commands[].name a quoted string, e.g. "speckit.myext.build".
  2. If the manifest is generated by a script, coerce names with str(name) before serialization and re-run install.
  3. Validate the manifest locally before install by json/yaml round-tripping and asserting isinstance(name, str) for each command entry.

Example fix

// before (manifest.json)
"commands": [{ "name": 2, "file": "commands/build.md" }]
// after
"commands": [{ "name": "speckit.myext.build", "file": "commands/build.md" }]
Defensive patterns

Strategy: validation

Validate before calling

def valid_command_entries(provides: dict) -> list[str]:
    errs = []
    for c in provides.get("commands", []):
        if not isinstance(c, dict):
            errs.append("non-mapping command entry")
        elif "name" in c and not isinstance(c["name"], str):
            errs.append(f"command name is {type(c['name']).__name__}, expected str")
    return errs

errs = valid_command_entries(manifest.get("provides", {}))
assert not errs, errs

Type guard

def is_str_command_name(cmd: object) -> bool:
    return isinstance(cmd, dict) and isinstance(cmd.get("name"), str)

Try / catch

try:
    ExtensionManifest.load(path)
except ValidationError as e:
    if "Invalid command name: expected a string" in str(e):
        # fix the manifest's provides.commands[].name and retry
        ...

Prevention

When it happens

Trigger: An extension.json/manifest file contains something like "commands": [{"name": 2, "file": "commands/foo.md"}] or a YAML manifest where the name is unquoted and parses as a number/boolean (e.g. name: 42, name: true). Loading the manifest via ExtensionManifest validation during 'specify extension install' or registry add raises ValidationError.

Common situations: Hand-editing a manifest and forgetting quotes around a numeric-looking name; YAML parsing turning an unquoted value into a non-string; programmatic manifest generation that inserts an int/enum instead of str.

Related errors


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