{"record":{"id":"8b20d9163aed6034","repo":"github/spec-kit","slug":"invalid-provides-commands-expected-a-list-8b20d9","errorCode":null,"errorMessage":"Invalid provides.commands: expected a list","messagePattern":"Invalid provides\\.commands: expected a list","errorType":"validation","errorClass":"IntegrationDescriptorError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/integrations/catalog.py","lineNumber":773,"sourceCode":"                if not isinstance(tool, dict):\n                    raise IntegrationDescriptorError(\n                        \"Each requires.tools entry must be a mapping\"\n                    )\n                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'\"","sourceCodeStart":755,"sourceCodeEnd":791,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/integrations/catalog.py#L755-L791","documentation":"Raised by IntegrationDescriptor._validate() (src/specify_cli/integrations/catalog.py:773) when parsing an integration.yml descriptor whose 'provides' mapping contains a 'commands' key that is not a YAML list. The library enforces a strict schema (schema_version 1.0) so that catalog tooling can rely on provides.commands being iterable. The check only fires when the key is explicitly present; omitting 'commands' entirely defaults it to an empty list.","triggerScenarios":"Constructing IntegrationDescriptor(descriptor_path) (or any catalog load that instantiates it) on an integration.yml where 'provides:' has 'commands:' mapped to a string, mapping, or scalar instead of a list — e.g. 'commands: plan' or 'commands: {name: plan}'.","commonSituations":"Hand-authoring an integration.yml for a custom integration and writing a single command as a scalar or a mapping instead of a one-element list; copy-pasting from a JSON-style config where the brackets were dropped; YAML indentation collapsing a list into a mapping.","solutions":["Open the integration.yml cited by the error and make provides.commands a YAML list of mappings.","If the integration provides only scripts, remove the 'commands' key entirely so the default [] applies.","Re-run the descriptor load (or 'specify integration' command) to confirm the error is gone."],"exampleFix":"# before\nprovides:\n  commands: plan\n\n# after\nprovides:\n  commands:\n    - name: plan\n      file: commands/plan.md","handlingStrategy":"validation","validationCode":"import yaml\n\ndef validate_provides_commands(path):\n    data = yaml.safe_load(path.read_text()) or {}\n    provides = data.get(\"provides\", {})\n    if \"commands\" in provides and not isinstance(provides[\"commands\"], list):\n        raise ValueError(\"provides.commands must be a list\")\n    return provides.get(\"commands\", [])","typeGuard":"def is_valid_commands_list(commands) -> bool:\n    return isinstance(commands, list) and all(\n        isinstance(c, dict) for c in commands\n    )","tryCatchPattern":"from specify_cli.integrations.catalog import IntegrationDescriptor, IntegrationDescriptorError\n\ntry:\n    desc = IntegrationDescriptor(path)\nexcept IntegrationDescriptorError as e:\n    print(f\"Bad descriptor {path}: {e}\")\n    raise SystemExit(1)","preventionTips":["Start integration.yml from an existing working descriptor and edit incrementally.","Run a YAML schema check (provides.commands must be a list of mappings) before loading.","Remember 'commands' is optional — omit it rather than writing a non-list value."],"tags":["yaml","validation","integration-descriptor","spec-kit"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}