{"record":{"id":"70e5c8cffa18646b","repo":"github/spec-kit","slug":"aliases-for-command-cmd-name-r-must-be-a-list","errorCode":null,"errorMessage":"Aliases for command {cmd_name!r} must be a list","messagePattern":"Aliases for command (.+?) must be a list","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/agents.py","lineNumber":702,"sourceCode":"            if _integ is not None:\n                _sep = _integ.invoke_separator_for_mode(registrar_writes_skills)\n        except (ImportError, ValueError, KeyError):\n            pass\n        _prefix = get_invocation_prefix(agent_name, registrar_writes_skills)\n\n        for cmd_info in commands:\n            cmd_name = cmd_info[\"name\"]\n            aliases = cmd_info.get(\"aliases\", [])\n            cmd_file = cmd_info[\"file\"]\n            name_reason = relative_extension_path_violation(cmd_name)\n            if name_reason:\n                raise ValueError(\n                    f\"Invalid command name {cmd_name!r}: {name_reason}\"\n                )\n            if aliases is None:\n                aliases = []\n            if not isinstance(aliases, list):\n                raise ValueError(\n                    f\"Aliases for command {cmd_name!r} must be a list\"\n                )\n            for alias in aliases:\n                alias_reason = relative_extension_path_violation(alias)\n                if alias_reason:\n                    raise ValueError(\n                        f\"Invalid command alias {alias!r}: {alias_reason}\"\n                    )\n\n            # Guard against path traversal using the single shared policy in\n            # relative_extension_path_violation(), so the runtime guard stays\n            # aligned with ExtensionManifest._validate() and the skill/preset\n            # readers. Skip a malformed/unsafe ``file`` (non-string, empty,\n            # whitespace, absolute/anchored, or ``..`` traversal); the\n            # resolve()/relative_to() check below is the final containment\n            # backstop.\n            if relative_extension_path_violation(cmd_file):\n                continue","sourceCodeStart":684,"sourceCodeEnd":720,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/agents.py#L684-L720","documentation":"For each command, the registrar reads the optional 'aliases' field. None and a missing key are tolerated (treated as no aliases), but any other non-list type — a string like \"a, b\", a dict, a number — raises ValueError('Aliases for command ... must be a list'). This is a schema check on the commands payload passed to register_commands().","triggerScenarios":"Passing aliases: \"plan, p\" (comma string) instead of [\"plan\", \"p\"]; aliases: {\"plan\": true}; aliases: 3; YAML extension manifests where aliases was written as a scalar and loaded verbatim.","commonSituations":"Hand-writing command descriptors and using a string shorthand that the API does not support; data coming from JSON/YAML configs where the author assumed string splitting; automated generators emitting null vs [] inconsistently (null is fine, scalars are not).","solutions":["Change the aliases value to a list of strings: aliases: [\"plan\", \"p\"].","Or remove the aliases key (or set it to null) when no aliases are wanted.","Add a pre-check in generators: assert aliases is None or (isinstance(aliases, list) and all(isinstance(a, str) for a in aliases))."],"exampleFix":"# before\n{\"name\": \"plan\", \"aliases\": \"plan, p\", \"file\": \"plan.md\"}\n# after\n{\"name\": \"plan\", \"aliases\": [\"plan\", \"p\"], \"file\": \"plan.md\"}","handlingStrategy":"type-guard","validationCode":"if any(\n    not (c.get(\"aliases\") is None or isinstance(c.get(\"aliases\"), list))\n    for c in commands\n):\n    raise SystemExit(\"every aliases field must be a list or null\")","typeGuard":"from typing import Any\n\ndef has_valid_aliases(cmd: dict[str, Any]) -> bool:\n    \"\"\"True when cmd['aliases'] is absent, None, or a list.\"\"\"\n    aliases = cmd.get(\"aliases\")\n    return aliases is None or isinstance(aliases, list)","tryCatchPattern":"try:\n    registrar.register_commands(...)\nexcept ValueError as exc:\n    if \"must be a list\" in str(exc):\n        raise SystemExit(f\"fix aliases schema: {exc}\") from exc\n    raise","preventionTips":["Always write aliases as a JSON/YAML list of strings (or omit the key).","Never use comma-separated strings for aliases.","Schema-check generated command descriptors before registration."],"tags":["commands","schema","validation","aliases"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}