{"record":{"id":"464d2d0eba41c4b7","repo":"github/spec-kit","slug":"invalid-hook-hook-name-expected-a-mapping-or","errorCode":null,"errorMessage":"Invalid hook '{hook_name}': expected a mapping or list of mappings","messagePattern":"Invalid hook '(.+?)': expected a mapping or list of mappings","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/extensions/__init__.py","lineNumber":420,"sourceCode":"            raise ValidationError(\n                \"Extension must provide at least one command, hook, or event \"\n                \"(or a declared template/script)\"\n            )\n\n        self._validate_provided_artifacts(templates, section=\"templates\", singular=\"template\")\n        self._validate_provided_artifacts(scripts, section=\"scripts\", singular=\"script\")\n\n        # Validate hook values (if present).\n        # Each event is a single mapping or a list of mappings.\n        if hooks:\n            for hook_name, hook_config in hooks.items():\n                if isinstance(hook_config, list) and not hook_config:\n                    raise ValidationError(\n                        f\"Invalid hook '{hook_name}': list must contain at least one entry\"\n                    )\n                for entry in coerce_hook_entries(hook_config):\n                    if not isinstance(entry, dict):\n                        raise ValidationError(\n                            f\"Invalid hook '{hook_name}': \"\n                            \"expected a mapping or list of mappings\"\n                        )\n                    if not entry.get(\"command\"):\n                        raise ValidationError(\n                            f\"Hook '{hook_name}' missing required 'command' field\"\n                        )\n                    if \"priority\" in entry:\n                        priority = entry[\"priority\"]\n                        if not isinstance(priority, int) or isinstance(priority, bool):\n                            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\"","sourceCodeStart":402,"sourceCodeEnd":438,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/extensions/__init__.py#L402-L438","documentation":"Each entry produced by coerce_hook_entries(hook_config) must be a mapping. coerce_hook_entries normalizes a single mapping or a list into entries; after normalization anything that is not a dict (e.g. a bare string entry inside the list) is rejected.","triggerScenarios":"`hooks:\\n  post-install:\\n    - ./setup.sh` — the list element is a string, not a mapping with a command key. The isinstance(entry, dict) check fails.","commonSituations":"Author writes hook entries as bare command strings, assuming the schema accepts a string shorthand, or mixes a string into an otherwise valid list of mappings.","solutions":["Make every entry a mapping: `- command: ./setup.sh`.","Add other supported keys (e.g. priority) inside the mapping, not as siblings.","A single mapping (no list) is also fine — the normalizer wraps it."],"exampleFix":"# before\nhooks:\n  post-install:\n    - ./setup.sh\n\n# after\nhooks:\n  post-install:\n    - command: ./setup.sh","handlingStrategy":"type-guard","validationCode":"def hook_entries_all_mappings(data: dict) -> bool:\n    hooks = data.get(\"hooks\") or {}\n    for cfg in hooks.values():\n        entries = cfg if isinstance(cfg, list) else [cfg]\n        if not all(isinstance(e, dict) for e in entries):\n            return False\n    return True","typeGuard":"def is_hook_entry(v: object) -> bool:\n    return isinstance(v, dict)","tryCatchPattern":null,"preventionTips":["Every hook entry is a mapping with a command key — no bare string shorthand.","A single mapping is fine; the loader normalizes it to a one-entry list."],"tags":["extension-manifest","validation","hooks"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}