github/spec-kit · error · ValidationError
Invalid events: expected a mapping
Error message
Invalid events: expected a mapping
What it means
Thrown by validate_events() while validating the `events` field of an extension manifest: `events` is present but is not a YAML mapping. The events system requires event-name -> config mappings; a list, string, or scalar at that position cannot be interpreted.
Source
Thrown at src/specify_cli/events.py:1781
install_integration_events(integration, project_root, manifest, events_map)
manifest.save()
except Exception as exc:
logger.warning("Failed to refresh events for '%s': %s", key, exc)
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)}"View on GitHub (pinned to bf88c9f9a8)
Solutions
- Rewrite `events` as a mapping keyed by event name with a per-event config mapping.
- Validate the manifest locally with yaml.safe_load before installing to confirm events is a dict.
- Re-install the extension.
Example fix
# before
events:
- pre_tool_use
- post_tool_use
# after
events:
pre_tool_use:
command: "./hook.sh"
post_tool_use:
command: "./after.sh" Defensive patterns
Strategy: type-guard
Validate before calling
data = yaml.safe_load(open(manifest_path))
events = data.get("events") if isinstance(data, dict) else None
if events is not None and not isinstance(events, dict):
raise SystemExit("events must be a mapping of event-name -> config") Type guard
def is_events_mapping(events) -> bool:
return events is None or isinstance(events, dict) Try / catch
except ValidationError as e:
if "expected a mapping" in str(e) and "events" in str(e):
rewrite_events_as_mapping() Prevention
- Model events as a dict keyed by event name, never a list.
- Round-trip manifests through yaml.safe_load in CI to catch shape errors.
When it happens
Trigger: An extension manifest contains `events:` followed by a sequence or scalar, e.g. `events: [pre_tool_use]` or `events: pre_tool_use`, and the manifest is loaded/validated during install, list, or event refresh.
Common situations: Authoring `events:` as a list of event names; a YAML indentation slip that attaches the events value to the wrong key; converting from another config format that models events as an array.
Related errors
- Invalid event '{event_name}': 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/1cc2a9e2a765d27d.
Report an issue: GitHub.