{"record":{"id":"def2f0fcc0baac9a","repo":"github/spec-kit","slug":"invalid-version-specifier-expected-a-string-got-def2f0","errorCode":null,"errorMessage":"Invalid version specifier: expected a string, got {type(required).__name__} ({required!r})","messagePattern":"Invalid version specifier: expected a string, got (.+?) \\((.+?)\\)","errorType":"validation","errorClass":"PresetCompatibilityError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/presets/__init__.py","lineNumber":808,"sourceCode":"            manifest: Preset manifest\n            speckit_version: Current spec-kit version\n\n        Returns:\n            True if compatible\n\n        Raises:\n            PresetCompatibilityError: If pack is incompatible\n        \"\"\"\n        required = manifest.requires_speckit_version\n        # Defense in depth: the manifest validator now rejects a non-string\n        # requires.speckit_version, but this method is public and also reachable\n        # with a hand-built manifest object. ``InvalidSpecifier`` alone does not\n        # cover a non-string -- scalars raise TypeError from the constructor, and\n        # a list/dict is iterable so it constructs here and only breaks inside\n        # .contains(). Reject up front so this always reports a\n        # PresetCompatibilityError.\n        if not isinstance(required, str):\n            raise PresetCompatibilityError(\n                \"Invalid version specifier: expected a string, got \"\n                f\"{type(required).__name__} ({required!r})\"\n            )\n        try:\n            SpecifierSet(required)  # Just to validate\n        except InvalidSpecifier:\n            raise PresetCompatibilityError(f\"Invalid version specifier: {required}\")\n\n        if not version_satisfies(speckit_version, required):\n            raise PresetCompatibilityError(\n                f\"Preset requires spec-kit {required}, \"\n                f\"but {speckit_version} is installed.\\n\"\n                f\"Upgrade spec-kit with: {REINSTALL_COMMAND}\"\n            )\n\n        return True\n\n    def _register_commands(","sourceCodeStart":790,"sourceCodeEnd":826,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/presets/__init__.py#L790-L826","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Ensure requires.speckit_version is a string specifier such as \">=1.2.0\", even in hand-built manifest objects.","In preset.yml, always quote the specifier: `speckit_version: \">=1.0.0\"` (a bare number YAML-parses to int/float).","Catch PresetCompatibilityError around check_compatibility when manifests come from untrusted sources."],"exampleFix":"# before\nmanifest = PresetManifest(path)\nmanifest.data[\"requires\"][\"speckit_version\"] = 2  # int\n\n# after\nmanifest.data[\"requires\"][\"speckit_version\"] = \">=2.0\"","handlingStrategy":"type-guard","validationCode":"req = manifest.requires_speckit_version\nif not isinstance(req, str):\n    raise ValueError(f\"requires.speckit_version must be str, got {type(req).__name__}\")","typeGuard":"def has_string_version_requirement(manifest) -> bool:\n    return isinstance(manifest.requires_speckit_version, str)","tryCatchPattern":"try:\n    manager.check_compatibility(manifest, speckit_version)\nexcept PresetCompatibilityError as e:\n    if \"expected a string\" in str(e):\n        # hand-built manifest has a non-string specifier; fix its data and retry\n        ...","preventionTips":["Always quote version specifiers in YAML: speckit_version: \">=1.0.0\" (bare numbers parse to int/float).","In tests and tooling, build manifests from real preset.yml files or assert the field type first."],"tags":["preset","compatibility","version","type-validation"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}