{"record":{"id":"a04112a173edcfad","repo":"github/spec-kit","slug":"invalid-event-event-name-expected-a-mapping","errorCode":null,"errorMessage":"Invalid event '{event_name}': expected a mapping","messagePattern":"Invalid event '(.+?)': expected a mapping","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/events.py","lineNumber":1785,"sourceCode":"            failures.append((key, str(exc)))\n\n    if failures:\n        raise EventRefreshError(failures)\n\n\n# -- Manifest validation ---------------------------------------------------\n\ndef validate_events(data: dict[str, Any]) -> None:\n    \"\"\"Validate ``events`` field in extension manifest data.\"\"\"\n    from .extensions import ValidationError\n\n    events = data.get(\"events\")\n    if \"events\" in data and not isinstance(events, dict):\n        raise ValidationError(\"Invalid events: expected a mapping\")\n    if events:\n        for event_name, event_config in events.items():\n            if not isinstance(event_config, dict):\n                raise ValidationError(\n                    f\"Invalid event '{event_name}': expected a mapping\"\n                )\n            command = event_config.get(\"command\")\n            # #17: command must be a non-empty string. A truthy non-string\n            # (e.g. command: [foo]) would pass a bare truthiness check and\n            # later render into invalid native configuration.\n            if not isinstance(command, str) or not command.strip():\n                raise ValidationError(\n                    f\"Event '{event_name}' missing required 'command' string\"\n                )\n            if event_name not in CANONICAL_EVENTS:\n                raise ValidationError(\n                    f\"Unknown event '{event_name}': \"\n                    f\"must be one of {sorted(CANONICAL_EVENTS)}\"\n                )\n            # C10: matcher must be a string (or absent). A non-string matcher\n            # such as `matcher: []` would later crash by_matcher.setdefault.\n            matcher = event_config.get(\"matcher\")","sourceCodeStart":1767,"sourceCodeEnd":1803,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/events.py#L1767-L1803","documentation":"Thrown by validate_events() when an entry under the manifest's `events` mapping is not itself a mapping. Each event name must map to a config dict (at minimum containing 'command'); a string, list, or null there fails this check.","triggerScenarios":"Manifest has `events: {pre_tool_use: \"./hook.sh\"}` (string instead of config mapping) or `events: {pre_tool_use:}` (null), and validate_events iterates event items and finds a non-dict event_config.","commonSituations":"Trying the shorthand `event: command-string` instead of `event: {command: ...}`; leaving an event section written but empty (YAML null); wrong indentation nesting the config one level off.","solutions":["Wrap each event's value in a mapping with at least a `command` key.","Check indentation: the `command:` line must be indented under the event name, not at the same column.","Re-run specify extension install/refresh."],"exampleFix":"# before\nevents:\n  pre_tool_use: ./hook.sh\n\n# after\nevents:\n  pre_tool_use:\n    command: \"./hook.sh\"","handlingStrategy":"type-guard","validationCode":"for name, cfg in (manifest.get(\"events\") or {}).items():\n    assert isinstance(cfg, dict), f\"event {name} config must be a mapping\"","typeGuard":"def all_event_configs_are_dicts(events: dict) -> bool:\n    return all(isinstance(c, dict) for c in events.values())","tryCatchPattern":"except ValidationError as e:\n    if \"Invalid event\" in str(e) and \"expected a mapping\" in str(e):\n        wrap_command_string(name, cfg)  # convert 'cmd' shorthand to {command: cmd}","preventionTips":["Never use the shorthand `event: command-string`; always nest a command: key.","Watch indentation — a config at the wrong level parses as a scalar."],"tags":["events","validation","yaml","manifest"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}