github/spec-kit · error · ValidationError
Invalid {singular} description for '{name}': expected a stri
Error message
Invalid {singular} description for '{name}': expected a string What it means
A provides.templates/scripts entry declared an optional 'description' whose value is not a string. Description is optional (absent is fine), but when present it must be a str — used for listing/help output, so ints, lists, or null are rejected with this ValidationError.
Source
Thrown at src/specify_cli/extensions/__init__.py:618
if not VALID_EXTENSION_ARTIFACT_NAME_PATTERN.match(name):
raise ValidationError(
f"Invalid {singular} name '{name}': "
"must be lowercase alphanumeric with hyphens only"
)
if name in seen_names:
raise ValidationError(
f"Duplicate {singular} name '{name}' in 'provides.{section}'"
)
seen_names.add(name)
file_value = entry["file"]
reason = relative_extension_path_violation(file_value)
if reason:
label = repr(file_value) if isinstance(file_value, str) else f"for {singular} '{name}'"
raise ValidationError(f"Invalid {singular} 'file' {label}: {reason}")
if "description" in entry and not isinstance(entry["description"], str):
raise ValidationError(
f"Invalid {singular} description for '{name}': expected a string"
)
if "strategy" in entry:
raise ValidationError(
f"Invalid {singular} entry '{name}': 'strategy' is not authorable for "
"extension-provided artifacts, which always use 'replace' semantics"
)
if section == "scripts" and "runtimes" in entry:
runtimes = entry["runtimes"]
if not isinstance(runtimes, list) or not all(
isinstance(r, str) for r in runtimes
):
raise ValidationError(
f"Invalid runtimes for script '{name}': expected a list of strings"
)
invalid = sorted(set(runtimes) - VALID_SCRIPT_RUNTIMES)View on GitHub (pinned to bf88c9f9a8)
Solutions
- Make the description a quoted string: "description": "Renders the plan document".
- Or remove the 'description' key entirely if you have nothing to say.
- In generated manifests, wrap descriptions in str() and skip None.
Example fix
// before
{ "name": "plan", "file": "templates/plan.md", "description": 5 }
// after
{ "name": "plan", "file": "templates/plan.md", "description": "Renders the plan document" } Defensive patterns
Strategy: type-guard
Validate before calling
for section in ("templates", "scripts"):
for e in manifest.get("provides", {}).get(section, []):
if isinstance(e, dict) and "description" in e:
assert isinstance(e["description"], str), f"non-string description for {e.get('name')}" Type guard
def has_str_description(entry: dict) -> bool:
return "description" not in entry or isinstance(entry["description"], str) Try / catch
try:
ExtensionManifest.load(path)
except ValidationError as e:
if "description" in str(e) and "expected a string" in str(e):
# quote the description or drop the key, then retry
... Prevention
- Descriptions are optional — omit rather than emit null.
- Wrap templated descriptions in str().
- Quote numbers in YAML descriptions.
When it happens
Trigger: {"name": "plan", "file": "...", "description": 5} or "description": ["a", "b"] or null. Raised by the isinstance check after path validation in the artifact validator.
Common situations: Template variables interpolating an empty/non-string default; YAML unquoted number as description; copying rich metadata from another schema.
Related errors
- Invalid command name: expected a string, got {type(cmd['name
- Aliases for command '{cmd['name']}' must be strings
- Invalid {singular} name: expected a string, got {type(name).
- 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/e8c80923c25cb2b7.
Report an issue: GitHub.