{"record":{"id":"0885897e40849dc0","repo":"github/spec-kit","slug":"aliases-for-command-cmd-name-must-be-string","errorCode":null,"errorMessage":"Aliases for command '{cmd['name']}' must be strings","messagePattern":"Aliases for command '(.+?)' must be strings","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/extensions/__init__.py","lineNumber":503,"sourceCode":"                    raise ValidationError(\n                        f\"Invalid command name '{cmd['name']}': \"\n                        \"must follow pattern 'speckit.{extension}.{command}'\"\n                    )\n\n            # Validate alias types; no pattern enforcement on aliases — they are\n            # intentionally free-form to preserve community extension compatibility\n            # (e.g. 'speckit.verify' short aliases used by existing extensions).\n            aliases = cmd.get(\"aliases\")\n            if aliases is None:\n                cmd[\"aliases\"] = []\n                aliases = []\n            if not isinstance(aliases, list):\n                raise ValidationError(\n                    f\"Aliases for command '{cmd['name']}' must be a list\"\n                )\n            for alias in aliases:\n                if not isinstance(alias, str):\n                    raise ValidationError(\n                        f\"Aliases for command '{cmd['name']}' must be strings\"\n                    )\n                alias_reason = relative_extension_path_violation(alias)\n                if alias_reason:\n                    raise ValidationError(\n                        f\"Invalid alias {alias!r} for command \"\n                        f\"'{cmd['name']}': {alias_reason}\"\n                    )\n\n        # Rewrite any hook command references that pointed at a renamed command or\n        # an alias-form ref (ext.cmd → speckit.ext.cmd).  Always emit a warning when\n        # the reference is changed so extension authors know to update the manifest.\n        for hook_name, hook_data in self.data.get(\"hooks\", {}).items():\n            for entry in coerce_hook_entries(hook_data):\n                if not isinstance(entry, dict):\n                    raise ValidationError(\n                        f\"Hook '{hook_name}' must be a mapping or list of mappings, \"\n                        f\"got {type(entry).__name__}\"","sourceCodeStart":485,"sourceCodeEnd":521,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/extensions/__init__.py#L485-L521","documentation":"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.","triggerScenarios":"\"aliases\": [\"build\", 42] or \"aliases\": [null]; YAML unquoted numeric alias like - 42. Raised inside the per-alias loop in ExtensionManifest._validate().","commonSituations":"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.","solutions":["Quote every alias in the manifest so it parses as a string: \"aliases\": [\"build\", \"42\"].","Remove null/entry elements from the aliases list.","If generating manifests programmatically, assert all(isinstance(a, str) for a in aliases) before writing."],"exampleFix":"// before\n\"aliases\": [\"build\", 42]\n// after\n\"aliases\": [\"build\", \"42\"]","handlingStrategy":"type-guard","validationCode":"for c in manifest.get(\"provides\", {}).get(\"commands\", []):\n    for a in (c.get(\"aliases\") or []):\n        if not isinstance(a, str):\n            raise SystemExit(f\"non-string alias {a!r} for {c.get('name')}\")","typeGuard":"def all_aliases_strings(cmd: dict) -> bool:\n    return all(isinstance(a, str) for a in (cmd.get(\"aliases\") or []))","tryCatchPattern":"try:\n    ExtensionManifest.load(path)\nexcept ValidationError as e:\n    if \"must be strings\" in str(e):\n        # quote the offending alias element and retry\n        ...","preventionTips":["Quote every alias in YAML/JSON.","Filter None out of generated alias lists.","Assert all(isinstance(a, str), aliases) in manifest generators."],"tags":["extensions","manifest","validation","aliases","type-error"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}