github/spec-kit · error · PresetCompatibilityError

Invalid version specifier: expected a string, got {type(requ

Error message

Invalid version specifier: expected a string, got {type(required).__name__} ({required!r})

What it means

check_compatibility rejects a manifest whose requires.speckit_version is not a string. This is defense in depth: the manifest validator already rejects non-string values, but check_compatibility is public and callable with a hand-built PresetManifest, so it re-checks and reports the offending type and value rather than letting packaging's SpecifierSet raise an opaque TypeError.

Source

Thrown at src/specify_cli/presets/__init__.py:808

            manifest: Preset manifest
            speckit_version: Current spec-kit version

        Returns:
            True if compatible

        Raises:
            PresetCompatibilityError: If pack is incompatible
        """
        required = manifest.requires_speckit_version
        # Defense in depth: the manifest validator now rejects a non-string
        # requires.speckit_version, but this method is public and also reachable
        # with a hand-built manifest object. ``InvalidSpecifier`` alone does not
        # cover a non-string -- scalars raise TypeError from the constructor, and
        # a list/dict is iterable so it constructs here and only breaks inside
        # .contains(). Reject up front so this always reports a
        # PresetCompatibilityError.
        if not isinstance(required, str):
            raise PresetCompatibilityError(
                "Invalid version specifier: expected a string, got "
                f"{type(required).__name__} ({required!r})"
            )
        try:
            SpecifierSet(required)  # Just to validate
        except InvalidSpecifier:
            raise PresetCompatibilityError(f"Invalid version specifier: {required}")

        if not version_satisfies(speckit_version, required):
            raise PresetCompatibilityError(
                f"Preset requires spec-kit {required}, "
                f"but {speckit_version} is installed.\n"
                f"Upgrade spec-kit with: {REINSTALL_COMMAND}"
            )

        return True

    def _register_commands(

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Ensure requires.speckit_version is a string specifier such as ">=1.2.0", even in hand-built manifest objects.
  2. In preset.yml, always quote the specifier: `speckit_version: ">=1.0.0"` (a bare number YAML-parses to int/float).
  3. Catch PresetCompatibilityError around check_compatibility when manifests come from untrusted sources.

Example fix

# before
manifest = PresetManifest(path)
manifest.data["requires"]["speckit_version"] = 2  # int

# after
manifest.data["requires"]["speckit_version"] = ">=2.0"
Defensive patterns

Strategy: type-guard

Validate before calling

req = manifest.requires_speckit_version
if not isinstance(req, str):
    raise ValueError(f"requires.speckit_version must be str, got {type(req).__name__}")

Type guard

def has_string_version_requirement(manifest) -> bool:
    return isinstance(manifest.requires_speckit_version, str)

Try / catch

try:
    manager.check_compatibility(manifest, speckit_version)
except PresetCompatibilityError as e:
    if "expected a string" in str(e):
        # hand-built manifest has a non-string specifier; fix its data and retry
        ...

Prevention

When it happens

Trigger: Constructing PresetManifest programmatically (or monkey-patching its requires_speckit_version) with an int/float/list/dict/None, then calling manager.check_compatibility(manifest, version). YAML sources hit the manifest validator instead, so this path is mostly tests and custom code.

Common situations: Test fixtures building manifests by hand with version=None, or tooling that injects a parsed YAML value (e.g. `>=1.0` quoted incorrectly becomes a string, but a bare numeric like `2` becomes int).

Related errors


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