github/spec-kit · error · ValidationError
Invalid extension.{field}: expected a string, got {type(ext[
Error message
Invalid extension.{field}: expected a string, got {type(ext[field]).__name__} What it means
Raised when one of the four extension metadata fields (id, name, version, description) is present but not a string. The guard exists because the values are later fed to re.match and packaging.Version, which raise bare TypeError on non-strings — and unquoted YAML makes that an easy slip (`version: 1.0` parses as float, `id: 2` as int). TypeError is not a ValidationError, so without this guard it escaped every malformed-manifest handler, including list_installed()'s 'Corrupted extension' fallback, crashing `specify extension list` for all extensions.
Source
Thrown at src/specify_cli/extensions/__init__.py:310
raise ValidationError(
f"Invalid extension: expected a mapping, got {type(ext).__name__}"
)
# Check presence AND type: the format/version checks below feed these
# values straight to ``re.match`` and ``packaging.Version``, both of
# which raise a bare TypeError on a non-string. YAML makes that an easy
# authoring slip -- unquoted ``version: 1.0`` parses as a float and
# ``id: 2`` as an int -- and TypeError is not a ValidationError, so it
# escapes every caller that already handles a malformed manifest (see
# list_installed()'s "Corrupted extension" fallback, which catches
# ValidationError only, making one bad extension exit ``specify
# extension list`` with a raw traceback and hide the healthy ones).
# Mirrors the sibling IntegrationDescriptor, which already type-checks
# the same four fields.
for field in ["id", "name", "version", "description"]:
if field not in ext:
raise ValidationError(f"Missing extension.{field}")
if not isinstance(ext[field], str):
raise ValidationError(
f"Invalid extension.{field}: expected a string, "
f"got {type(ext[field]).__name__}"
)
# Validate extension ID format
if not re.match(r"^[a-z0-9-]+$", ext["id"]):
raise ValidationError(
f"Invalid extension ID '{ext['id']}': "
"must be lowercase alphanumeric with hyphens only"
)
# Validate semantic version
try:
pkg_version.Version(ext["version"])
except pkg_version.InvalidVersion:
raise ValidationError(f"Invalid version: {ext['version']}")
# Validate optional category field (free-form string)View on GitHub (pinned to bf88c9f9a8)
Solutions
- Quote the offending value so YAML yields a string, e.g. `version: "1.0.0"` and `id: "ext-2"`.
- Read the type name in the message: 'float' almost always means unquoted version.
- Re-validate with specify extension list/install.
Example fix
# before extension: id: my-ext version: 1.0.0 # parses as float? no - but 1.0 does # after extension: id: my-ext version: "1.0.0"
Defensive patterns
Strategy: type-guard
Validate before calling
for f in ["id", "name", "version", "description"]:
if not isinstance(data["extension"].get(f), str):
data["extension"][f] = str(data["extension"][f]) # or fail fast Type guard
def extension_fields_are_strings(ext: dict) -> bool:
return all(isinstance(ext.get(f), str) for f in ("id", "name", "version", "description")) Try / catch
except ValidationError as e:
if "expected a string" in str(e):
quote_yaml_scalars(path) # re-save with version: "1.0.0" style quoting Prevention
- Always quote version (and any numeric-looking value) in YAML.
- Remember YAML type coercion: 1.0 -> float, 2 -> int, no -> bool; manifests want strings.
When it happens
Trigger: Manifest contains unquoted `version: 1.0` (YAML float), `id: 2` (int), or any field given as a list/bool; the isinstance(ext[field], str) check fails and names the actual type.
Common situations: Forgetting quotes on version — the classic case, since semvers look like numbers; numeric ids in examples; YAML boolean coercion for `name: no`. Mirrors the same four-field type check in IntegrationDescriptor.
Related errors
- Manifest must be a YAML mapping, got {type(data).__name__}:
- Invalid extension: expected a mapping, got {type(ext).__name
- Manifest must be a YAML mapping at the top level.
- Manifest is missing the required 'bundle' mapping.
- 'requires' must be a mapping when present.
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/cf4511c832269434.
Report an issue: GitHub.