github/spec-kit · error · ValueError
Aliases for command {cmd_name!r} must be a list
Error message
Aliases for command {cmd_name!r} must be a list What it means
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().
Source
Thrown at src/specify_cli/agents.py:702
if _integ is not None:
_sep = _integ.invoke_separator_for_mode(registrar_writes_skills)
except (ImportError, ValueError, KeyError):
pass
_prefix = get_invocation_prefix(agent_name, registrar_writes_skills)
for cmd_info in commands:
cmd_name = cmd_info["name"]
aliases = cmd_info.get("aliases", [])
cmd_file = cmd_info["file"]
name_reason = relative_extension_path_violation(cmd_name)
if name_reason:
raise ValueError(
f"Invalid command name {cmd_name!r}: {name_reason}"
)
if aliases is None:
aliases = []
if not isinstance(aliases, list):
raise ValueError(
f"Aliases for command {cmd_name!r} must be a list"
)
for alias in aliases:
alias_reason = relative_extension_path_violation(alias)
if alias_reason:
raise ValueError(
f"Invalid command alias {alias!r}: {alias_reason}"
)
# Guard against path traversal using the single shared policy in
# relative_extension_path_violation(), so the runtime guard stays
# aligned with ExtensionManifest._validate() and the skill/preset
# readers. Skip a malformed/unsafe ``file`` (non-string, empty,
# whitespace, absolute/anchored, or ``..`` traversal); the
# resolve()/relative_to() check below is the final containment
# backstop.
if relative_extension_path_violation(cmd_file):
continueView on GitHub (pinned to bf88c9f9a8)
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)).
Example fix
# before
{"name": "plan", "aliases": "plan, p", "file": "plan.md"}
# after
{"name": "plan", "aliases": ["plan", "p"], "file": "plan.md"} Defensive patterns
Strategy: type-guard
Validate before calling
if any(
not (c.get("aliases") is None or isinstance(c.get("aliases"), list))
for c in commands
):
raise SystemExit("every aliases field must be a list or null") Type guard
from typing import Any
def has_valid_aliases(cmd: dict[str, Any]) -> bool:
"""True when cmd['aliases'] is absent, None, or a list."""
aliases = cmd.get("aliases")
return aliases is None or isinstance(aliases, list) Try / catch
try:
registrar.register_commands(...)
except ValueError as exc:
if "must be a list" in str(exc):
raise SystemExit(f"fix aliases schema: {exc}") from exc
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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).
Related errors
- Output path {candidate!r} escapes directory {base!r}
- Invalid command name {cmd_name!r}: {name_reason}
- Invalid command alias {alias!r}: {alias_reason}
- {source} contains an invalid bundle manifest:\n - " + "\n
- Hook '{hook_name}' has invalid 'priority': must be >= 1
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/70e5c8cffa18646b.
Report an issue: GitHub.