github/spec-kit · error · ValidationError

Aliases for command '{cmd['name']}' must be strings

Error message

Aliases for command '{cmd['name']}' must be strings

What it means

One element inside a command's 'aliases' list is not a string (int, dict, null, etc.). After the list-shape check passes, the loader iterates each alias and enforces str type before running the shared path-safety policy on it. Free-form naming is allowed, but type safety is not.

Source

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

                    raise ValidationError(
                        f"Invalid command name '{cmd['name']}': "
                        "must follow pattern 'speckit.{extension}.{command}'"
                    )

            # Validate alias types; no pattern enforcement on aliases — they are
            # intentionally free-form to preserve community extension compatibility
            # (e.g. 'speckit.verify' short aliases used by existing extensions).
            aliases = cmd.get("aliases")
            if aliases is None:
                cmd["aliases"] = []
                aliases = []
            if not isinstance(aliases, list):
                raise ValidationError(
                    f"Aliases for command '{cmd['name']}' must be a list"
                )
            for alias in aliases:
                if not isinstance(alias, str):
                    raise ValidationError(
                        f"Aliases for command '{cmd['name']}' must be strings"
                    )
                alias_reason = relative_extension_path_violation(alias)
                if alias_reason:
                    raise ValidationError(
                        f"Invalid alias {alias!r} for command "
                        f"'{cmd['name']}': {alias_reason}"
                    )

        # Rewrite any hook command references that pointed at a renamed command or
        # an alias-form ref (ext.cmd → speckit.ext.cmd).  Always emit a warning when
        # the reference is changed so extension authors know to update the manifest.
        for hook_name, hook_data in self.data.get("hooks", {}).items():
            for entry in coerce_hook_entries(hook_data):
                if not isinstance(entry, dict):
                    raise ValidationError(
                        f"Hook '{hook_name}' must be a mapping or list of mappings, "
                        f"got {type(entry).__name__}"

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Quote every alias in the manifest so it parses as a string: "aliases": ["build", "42"].
  2. Remove null/entry elements from the aliases list.
  3. If generating manifests programmatically, assert all(isinstance(a, str) for a in aliases) before writing.

Example fix

// before
"aliases": ["build", 42]
// after
"aliases": ["build", "42"]
Defensive patterns

Strategy: type-guard

Validate before calling

for c in manifest.get("provides", {}).get("commands", []):
    for a in (c.get("aliases") or []):
        if not isinstance(a, str):
            raise SystemExit(f"non-string alias {a!r} for {c.get('name')}")

Type guard

def all_aliases_strings(cmd: dict) -> bool:
    return all(isinstance(a, str) for a in (cmd.get("aliases") or []))

Try / catch

try:
    ExtensionManifest.load(path)
except ValidationError as e:
    if "must be strings" in str(e):
        # quote the offending alias element and retry
        ...

Prevention

When it happens

Trigger: "aliases": ["build", 42] or "aliases": [null]; YAML unquoted numeric alias like - 42. Raised inside the per-alias loop in ExtensionManifest._validate().

Common situations: Mixing data from config templating where a variable interpolates as a number; YAML parsing an unquoted alias into an int/bool; null sneaking in from optional fields of a generator script.

Related errors


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