github/spec-kit · error · ValidationError
Invalid event '{event_name}': expected a mapping
Error message
Invalid event '{event_name}': expected a mapping What it means
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.
Source
Thrown at src/specify_cli/events.py:1785
failures.append((key, str(exc)))
if failures:
raise EventRefreshError(failures)
# -- Manifest validation ---------------------------------------------------
def validate_events(data: dict[str, Any]) -> None:
"""Validate ``events`` field in extension manifest data."""
from .extensions import ValidationError
events = data.get("events")
if "events" in data and not isinstance(events, dict):
raise ValidationError("Invalid events: expected a mapping")
if events:
for event_name, event_config in events.items():
if not isinstance(event_config, dict):
raise ValidationError(
f"Invalid event '{event_name}': expected a mapping"
)
command = event_config.get("command")
# #17: command must be a non-empty string. A truthy non-string
# (e.g. command: [foo]) would pass a bare truthiness check and
# later render into invalid native configuration.
if not isinstance(command, str) or not command.strip():
raise ValidationError(
f"Event '{event_name}' missing required 'command' string"
)
if event_name not in CANONICAL_EVENTS:
raise ValidationError(
f"Unknown event '{event_name}': "
f"must be one of {sorted(CANONICAL_EVENTS)}"
)
# C10: matcher must be a string (or absent). A non-string matcher
# such as `matcher: []` would later crash by_matcher.setdefault.
matcher = event_config.get("matcher")View on GitHub (pinned to bf88c9f9a8)
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.
Example fix
# before
events:
pre_tool_use: ./hook.sh
# after
events:
pre_tool_use:
command: "./hook.sh" Defensive patterns
Strategy: type-guard
Validate before calling
for name, cfg in (manifest.get("events") or {}).items():
assert isinstance(cfg, dict), f"event {name} config must be a mapping" Type guard
def all_event_configs_are_dicts(events: dict) -> bool:
return all(isinstance(c, dict) for c in events.values()) Try / catch
except ValidationError as e:
if "Invalid event" in str(e) and "expected a mapping" in str(e):
wrap_command_string(name, cfg) # convert 'cmd' shorthand to {command: cmd} Prevention
- Never use the shorthand `event: command-string`; always nest a command: key.
- Watch indentation — a config at the wrong level parses as a scalar.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Invalid events: expected a mapping
- Manifest must be a YAML mapping at the top level.
- Manifest is missing the required 'bundle' mapping.
- 'requires' must be a mapping when present.
- 'integration' must be a mapping when present.
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/a04112a173edcfad.
Report an issue: GitHub.