{"record":{"id":"e8c80923c25cb2b7","repo":"github/spec-kit","slug":"invalid-singular-description-for-name-expec","errorCode":null,"errorMessage":"Invalid {singular} description for '{name}': expected a string","messagePattern":"Invalid (.+?) description for '(.+?)': expected a string","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/extensions/__init__.py","lineNumber":618,"sourceCode":"            if not VALID_EXTENSION_ARTIFACT_NAME_PATTERN.match(name):\n                raise ValidationError(\n                    f\"Invalid {singular} name '{name}': \"\n                    \"must be lowercase alphanumeric with hyphens only\"\n                )\n            if name in seen_names:\n                raise ValidationError(\n                    f\"Duplicate {singular} name '{name}' in 'provides.{section}'\"\n                )\n            seen_names.add(name)\n\n            file_value = entry[\"file\"]\n            reason = relative_extension_path_violation(file_value)\n            if reason:\n                label = repr(file_value) if isinstance(file_value, str) else f\"for {singular} '{name}'\"\n                raise ValidationError(f\"Invalid {singular} 'file' {label}: {reason}\")\n\n            if \"description\" in entry and not isinstance(entry[\"description\"], str):\n                raise ValidationError(\n                    f\"Invalid {singular} description for '{name}': expected a string\"\n                )\n\n            if \"strategy\" in entry:\n                raise ValidationError(\n                    f\"Invalid {singular} entry '{name}': 'strategy' is not authorable for \"\n                    \"extension-provided artifacts, which always use 'replace' semantics\"\n                )\n\n            if section == \"scripts\" and \"runtimes\" in entry:\n                runtimes = entry[\"runtimes\"]\n                if not isinstance(runtimes, list) or not all(\n                    isinstance(r, str) for r in runtimes\n                ):\n                    raise ValidationError(\n                        f\"Invalid runtimes for script '{name}': expected a list of strings\"\n                    )\n                invalid = sorted(set(runtimes) - VALID_SCRIPT_RUNTIMES)","sourceCodeStart":600,"sourceCodeEnd":636,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/extensions/__init__.py#L600-L636","documentation":"A provides.templates/scripts entry declared an optional 'description' whose value is not a string. Description is optional (absent is fine), but when present it must be a str — used for listing/help output, so ints, lists, or null are rejected with this ValidationError.","triggerScenarios":"{\"name\": \"plan\", \"file\": \"...\", \"description\": 5} or \"description\": [\"a\", \"b\"] or null. Raised by the isinstance check after path validation in the artifact validator.","commonSituations":"Template variables interpolating an empty/non-string default; YAML unquoted number as description; copying rich metadata from another schema.","solutions":["Make the description a quoted string: \"description\": \"Renders the plan document\".","Or remove the 'description' key entirely if you have nothing to say.","In generated manifests, wrap descriptions in str() and skip None."],"exampleFix":"// before\n{ \"name\": \"plan\", \"file\": \"templates/plan.md\", \"description\": 5 }\n// after\n{ \"name\": \"plan\", \"file\": \"templates/plan.md\", \"description\": \"Renders the plan document\" }","handlingStrategy":"type-guard","validationCode":"for section in (\"templates\", \"scripts\"):\n    for e in manifest.get(\"provides\", {}).get(section, []):\n        if isinstance(e, dict) and \"description\" in e:\n            assert isinstance(e[\"description\"], str), f\"non-string description for {e.get('name')}\"","typeGuard":"def has_str_description(entry: dict) -> bool:\n    return \"description\" not in entry or isinstance(entry[\"description\"], str)","tryCatchPattern":"try:\n    ExtensionManifest.load(path)\nexcept ValidationError as e:\n    if \"description\" in str(e) and \"expected a string\" in str(e):\n        # quote the description or drop the key, then retry\n        ...","preventionTips":["Descriptions are optional — omit rather than emit null.","Wrap templated descriptions in str().","Quote numbers in YAML descriptions."],"tags":["extensions","manifest","validation","type-error"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}