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
- Open the extension manifest and make every provides.commands[].name a quoted string, e.g. "speckit.myext.build".
- If the manifest is generated by a script, coerce names with str(name) before serialization and re-run install.
- 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
- Always quote command names in JSON/YAML manifests.
- If generating manifests, run a schema pass asserting name/file types before writing.
- Keep a known-good bundled extension manifest as the template.
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
- Aliases for command '{cmd['name']}' must be strings
- Invalid {singular} name: expected a string, got {type(name).
- Invalid {singular} description for '{name}': expected a stri
- Invalid runtimes for script '{name}': expected a list of str
- Manifest must be a YAML mapping, got {type(data).__name__}:
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/79a574d99a8dac87.
Report an issue: GitHub.