github/spec-kit · error · ValidationError

Invalid extension ID '{ext['id']}': must be lowercase alphan

Error message

Invalid extension ID '{ext['id']}': must be lowercase alphanumeric with hyphens only

What it means

Thrown by ExtensionManifest validation while parsing an extension's manifest (extension.yml). The extension.id field must match ^[a-z0-9-]+$ — lowercase letters, digits, and hyphens only. Any uppercase letter, underscore, space, or symbol in the ID fails the regex and aborts extension loading with a ValidationError.

Source

Thrown at src/specify_cli/extensions/__init__.py:317

        # ``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)
        if "category" in ext:
            if not isinstance(ext["category"], str) or not ext["category"].strip():
                raise ValidationError(
                    "Invalid extension.category: must be a non-empty string"
                )

        # Validate optional effect field

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Set extension.id to a lowercase slug, e.g. `id: agent-context`.
  2. Remove underscores, dots, spaces, and uppercase characters from the id value in extension.yml.
  3. Rename the extension directory to match the corrected id so the registry stays consistent.

Example fix

# before (extension.yml)
id: My_Agent.Context

# after
id: my-agent-context
Defensive patterns

Strategy: validation

Validate before calling

import re

def valid_extension_id(ext_id: object) -> bool:
    return isinstance(ext_id, str) and re.fullmatch(r"[a-z0-9-]+", ext_id) is not None

Type guard

def is_valid_extension_id(v: object) -> bool:
    return isinstance(v, str) and bool(re.fullmatch(r"[a-z0-9-]+", v))

Try / catch

try:
    manifest = ExtensionManifest.load(path)
except ValidationError as e:
    print(f"Manifest invalid: {e}")

Prevention

When it happens

Trigger: An extension manifest with `id: My_Extension`, `id: "agent context"`, or `id: ctx.v2` is loaded (via `specify extension add` or at init when bundled extensions install). re.match(r"^[a-z0-9-]+$", ext["id"]) returns None and the error is raised.

Common situations: Authors naming an extension after a GitHub repo with dots (my-ext.repo), copy-pasting a display name into the id field, or converting underscores from a Python package name. Directory name and id mismatch also surfaces here.

Related errors


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