{"record":{"id":"f834468a2d9279c4","repo":"github/spec-kit","slug":"invalid-extension-category-must-be-a-non-empty-st","errorCode":null,"errorMessage":"Invalid extension.category: must be a non-empty string","messagePattern":"Invalid extension\\.category: must be a non-empty string","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/extensions/__init__.py","lineNumber":331,"sourceCode":"                )\n\n        # Validate extension ID format\n        if not re.match(r\"^[a-z0-9-]+$\", ext[\"id\"]):\n            raise ValidationError(\n                f\"Invalid extension ID '{ext['id']}': \"\n                \"must be lowercase alphanumeric with hyphens only\"\n            )\n\n        # Validate semantic version\n        try:\n            pkg_version.Version(ext[\"version\"])\n        except pkg_version.InvalidVersion:\n            raise ValidationError(f\"Invalid version: {ext['version']}\")\n\n        # Validate optional category field (free-form string)\n        if \"category\" in ext:\n            if not isinstance(ext[\"category\"], str) or not ext[\"category\"].strip():\n                raise ValidationError(\n                    \"Invalid extension.category: must be a non-empty string\"\n                )\n\n        # Validate optional effect field\n        if \"effect\" in ext:\n            if not isinstance(ext[\"effect\"], str) or ext[\"effect\"] not in VALID_EFFECTS:\n                raise ValidationError(\n                    f\"Invalid extension.effect '{ext.get('effect')}': \"\n                    f\"must be one of {sorted(VALID_EFFECTS)}\"\n                )\n\n        # Validate requires section\n        requires = self.data[\"requires\"]\n        if not isinstance(requires, dict):\n            raise ValidationError(\n                f\"Invalid requires: expected a mapping, got {type(requires).__name__}\"\n            )\n        if \"speckit_version\" not in requires:","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/extensions/__init__.py#L313-L349","documentation":"The optional extension.category field, when present, must be a non-empty string (after strip). It is free-form (any non-blank text is accepted) but a blank string, null-adjacent value, or non-string (list, number) fails validation.","triggerScenarios":"A manifest contains `category: \"\"`, `category: \"   \"`, or `category: [tooling]`. The isinstance/strip check fails and ValidationError is raised during manifest load.","commonSituations":"Templates with a placeholder `category: \"\"` left unfilled, YAML flow syntax accidentally making category a list, or a commented-out category where a stray empty value remains.","solutions":["Provide a meaningful non-empty category, e.g. `category: tooling`.","Or delete the category key entirely — it is optional and absence passes validation.","Ensure the value is a plain quoted string, not a list or blank."],"exampleFix":"# before\ncategory: \"\"\n\n# after\ncategory: tooling","handlingStrategy":"validation","validationCode":"def valid_category(cat: object) -> bool:\n    return not isinstance(cat, str) or bool(cat.strip())  # absent is OK; if present must be non-blank str\n\ndef category_ok(data: dict) -> bool:\n    cat = data.get(\"extension\", {}).get(\"category\")\n    return cat is None or (isinstance(cat, str) and cat.strip())","typeGuard":"def is_nonempty_str(v: object) -> bool:\n    return isinstance(v, str) and bool(v.strip())","tryCatchPattern":null,"preventionTips":["Either fill category with a real label or delete the key — empty placeholders fail.","Use a YAML schema validator (e.g. yamllint plus a jsonschema for extension.yml) in CI."],"tags":["extension-manifest","validation","optional-fields"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}