{"record":{"id":"e1355d062f0bc17b","repo":"github/spec-kit","slug":"invalid-provides-scripts-expected-a-list-e1355d","errorCode":null,"errorMessage":"Invalid provides.scripts: expected a list","messagePattern":"Invalid provides\\.scripts: expected a list","errorType":"validation","errorClass":"IntegrationDescriptorError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/integrations/catalog.py","lineNumber":777,"sourceCode":"                tool_name = tool.get(\"name\")\n                if not isinstance(tool_name, str) or not tool_name.strip():\n                    raise IntegrationDescriptorError(\n                        \"requires.tools entry 'name' must be a non-empty string\"\n                    )\n\n        provides = self.data[\"provides\"]\n        if not isinstance(provides, dict):\n            raise IntegrationDescriptorError(\n                \"'provides' must be a mapping\"\n            )\n        commands = provides.get(\"commands\", [])\n        scripts = provides.get(\"scripts\", [])\n        if \"commands\" in provides and not isinstance(commands, list):\n            raise IntegrationDescriptorError(\n                \"Invalid provides.commands: expected a list\"\n            )\n        if \"scripts\" in provides and not isinstance(scripts, list):\n            raise IntegrationDescriptorError(\n                \"Invalid provides.scripts: expected a list\"\n            )\n        if not commands and not scripts:\n            raise IntegrationDescriptorError(\n                \"Integration must provide at least one command or script\"\n            )\n        for cmd in commands:\n            if not isinstance(cmd, dict):\n                raise IntegrationDescriptorError(\n                    \"Each command entry must be a mapping\"\n                )\n            if \"name\" not in cmd or \"file\" not in cmd:\n                raise IntegrationDescriptorError(\n                    \"Command entry missing 'name' or 'file'\"\n                )\n            cmd_name = cmd[\"name\"]\n            cmd_file = cmd[\"file\"]\n            if not isinstance(cmd_name, str) or not cmd_name.strip():","sourceCodeStart":759,"sourceCodeEnd":795,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/integrations/catalog.py#L759-L795","documentation":"Raised by IntegrationDescriptor._validate() (src/specify_cli/integrations/catalog.py:777) when an integration.yml 'provides' mapping declares 'scripts' with a non-list value. The validator treats provides.scripts as an optional list of relative script path strings; a present-but-wrong-typed value aborts descriptor loading with IntegrationDescriptorError.","triggerScenarios":"Loading a descriptor whose 'provides:' block has 'scripts:' set to a scalar, mapping, or null-adjacent value (e.g. 'scripts: setup.sh' instead of a list), causing IntegrationDescriptor(path) to raise during _validate().","commonSituations":"Authoring provides.scripts as a single path string because the integration has only one script; merging descriptors from a format where scripts were a map of names to paths; YAML flow-syntax mistakes.","solutions":["Change provides.scripts in the offending integration.yml to a YAML list of strings.","If no scripts are provided, delete the 'scripts' key so the default empty list is used.","Validate the file again with the catalog loader or the relevant specify command."],"exampleFix":"# before\nprovides:\n  scripts: scripts/bash/setup.sh\n\n# after\nprovides:\n  scripts:\n    - scripts/bash/setup.sh","handlingStrategy":"validation","validationCode":"import yaml\n\ndef validate_provides_scripts(path):\n    data = yaml.safe_load(path.read_text()) or {}\n    provides = data.get(\"provides\", {})\n    if \"scripts\" in provides and not isinstance(provides[\"scripts\"], list):\n        raise ValueError(\"provides.scripts must be a list of strings\")\n    return provides.get(\"scripts\", [])","typeGuard":"def is_valid_scripts_list(scripts) -> bool:\n    return isinstance(scripts, list) and all(\n        isinstance(s, str) and s.strip() for s in scripts\n    )","tryCatchPattern":"try:\n    desc = IntegrationDescriptor(path)\nexcept IntegrationDescriptorError as e:\n    if \"provides.scripts\" in str(e):\n        fix_scripts_key(path)  # rewrite as list or remove key\n    else:\n        raise","preventionTips":["Always write scripts as a YAML list even for a single script.","Omit the 'scripts' key entirely when there are none.","Lint descriptors in CI with a small schema validator."],"tags":["yaml","validation","integration-descriptor","spec-kit"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}