{"record":{"id":"fd768a5003b30a78","repo":"github/spec-kit","slug":"each-command-entry-must-be-a-mapping","errorCode":null,"errorMessage":"Each command entry must be a mapping","messagePattern":"Each command entry must be a mapping","errorType":"validation","errorClass":"IntegrationDescriptorError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/integrations/catalog.py","lineNumber":786,"sourceCode":"                \"'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():\n                raise IntegrationDescriptorError(\n                    \"Command entry 'name' must be a non-empty string\"\n                )\n            if not isinstance(cmd_file, str) or not cmd_file.strip():\n                raise IntegrationDescriptorError(\n                    \"Command entry 'file' must be a non-empty string\"\n                )\n            if os.path.isabs(cmd_file) or \"..\" in Path(cmd_file).parts or Path(cmd_file).drive or Path(cmd_file).anchor:\n                raise IntegrationDescriptorError(","sourceCodeStart":768,"sourceCodeEnd":804,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/integrations/catalog.py#L768-L804","documentation":"Raised by IntegrationDescriptor._validate() (src/specify_cli/integrations/catalog.py:786) while iterating provides.commands: an element of the commands list is not a YAML mapping. Each command entry must be a dict (later checked for 'name' and 'file' keys), so scalar or nested-list entries are rejected.","triggerScenarios":"A descriptor with 'provides: commands: [plan, build]' or a list of lists; the for-loop over commands hits a non-dict element and raises IntegrationDescriptorError immediately.","commonSituations":"Writing a shorthand list of command names instead of full mappings; converting a JSON list of strings into YAML without wrapping each in an object; inconsistent entry formats within one list.","solutions":["Wrap every element of provides.commands in a mapping with at least 'name' and 'file' keys.","Check for mixed entries (some dicts, some strings) — all must be mappings.","Re-validate the descriptor."],"exampleFix":"# before\nprovides:\n  commands:\n    - plan\n    - build\n\n# after\nprovides:\n  commands:\n    - name: plan\n      file: commands/plan.md\n    - name: build\n      file: commands/build.md","handlingStrategy":"validation","validationCode":"def valid_command_entries(commands):\n    return all(isinstance(c, dict) for c in commands)","typeGuard":"from typing import Any\n\ndef is_command_entry(entry: Any) -> bool:\n    return isinstance(entry, dict)","tryCatchPattern":null,"preventionTips":["Each list item under commands must be a mapping with name/file keys.","Never mix bare strings into the commands list.","Copy an existing validated entry as the shape reference."],"tags":["yaml","validation","integration-descriptor","spec-kit"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}