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

  1. Quote the offending value so YAML yields a string, e.g. `version: "1.0.0"` and `id: "ext-2"`.
  2. Read the type name in the message: 'float' almost always means unquoted version.
  3. 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

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


AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14). Data as JSON: /api/errors/cf4511c832269434. Report an issue: GitHub.