{"record":{"id":"bdec75343f298e46","repo":"github/spec-kit","slug":"command-missing-name-or-file","errorCode":null,"errorMessage":"Command missing 'name' or 'file'","messagePattern":"Command missing 'name' or 'file'","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/extensions/__init__.py","lineNumber":449,"sourceCode":"                            raise ValidationError(\n                                f\"Hook '{hook_name}' has invalid 'priority': \"\n                                \"must be an integer\"\n                            )\n                        if priority < 1:\n                            raise ValidationError(\n                                f\"Hook '{hook_name}' has invalid 'priority': \"\n                                \"must be >= 1\"\n                            )\n\n        # Validate commands; track renames so hook references can be rewritten.\n        rename_map: Dict[str, str] = {}\n        for cmd in commands:\n            if not isinstance(cmd, dict):\n                raise ValidationError(\n                    \"Each command entry in 'provides.commands' must be a mapping\"\n                )\n            if \"name\" not in cmd or \"file\" not in cmd:\n                raise ValidationError(\"Command missing 'name' or 'file'\")\n            # The pattern match below would raise a bare TypeError on a\n            # non-string name (``name: 2``), escaping the ValidationError\n            # contract. The 'file' field needs no check here:\n            # relative_extension_path_violation() below already rejects a\n            # non-string value.\n            if not isinstance(cmd[\"name\"], str):\n                raise ValidationError(\n                    f\"Invalid command name: expected a string, \"\n                    f\"got {type(cmd['name']).__name__}\"\n                )\n\n            # Validate the 'file' field at manifest-load time using the single\n            # shared policy in relative_extension_path_violation(), so manifest\n            # validation cannot drift from the runtime registrar guard. This is\n            # defense-in-depth: the command/skill/preset readers also contain\n            # the resolved path, but rejecting an unsafe value here surfaces a\n            # clear error instead of silently skipping the command.\n            cmd_file = cmd[\"file\"]","sourceCodeStart":431,"sourceCodeEnd":467,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/extensions/__init__.py#L431-L467","documentation":"The command name in provides.commands must be a string. The source comment explains the guard exists because the pattern match used further down would raise a bare TypeError on a non-string name (`name: 2`), escaping the ValidationError contract; the file field needs no such check since relative_extension_path_violation() already rejects non-strings.","triggerScenarios":"`name: 2` or `name: [check]` in a command entry — YAML coerces unquoted values to int/list. The isinstance(cmd[\"name\"], str) check fails and names the actual type.","commonSituations":"Numeric command names (e.g. `name: 2024-report` quoted incorrectly so YAML sees `2024` as int), or names written as YAML flow lists.","solutions":["Quote the name so YAML keeps it a string: `name: \"2024-report\"`.","Use a word-first slug name to avoid YAML number coercion entirely.","Check the reported type name in the error — int/float/list each point at a missing quote."],"exampleFix":"# before (YAML parses 2024-report as int for `2024-report`? no — but `2024` alone is)\n- name: 2024\n\n# after\n- name: \"2024-report\"","handlingStrategy":"type-guard","validationCode":"def command_names_are_strings(data: dict) -> bool:\n    cmds = (data.get(\"provides\") or {}).get(\"commands\") or []\n    return all(isinstance(c.get(\"name\"), str) for c in cmds if isinstance(c, dict))","typeGuard":"def is_string_command_name(v: object) -> bool:\n    return isinstance(v, str)","tryCatchPattern":null,"preventionTips":["Quote command names that start with digits so YAML keeps them strings.","This guard exists because the downstream regex match would raise a bare TypeError — so always ship string names."],"tags":["extension-manifest","validation","commands","yaml-coercion","typing"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}