{"record":{"id":"38277e3ed68cc511","repo":"github/spec-kit","slug":"unknown-event-event-name-must-be-one-of-sort","errorCode":null,"errorMessage":"Unknown event '{event_name}': must be one of {sorted(CANONICAL_EVENTS)}","messagePattern":"Unknown event '(.+?)': must be one of (.+?)","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"warning","filePath":"src/specify_cli/events.py","lineNumber":802,"sourceCode":"        if not isinstance(entry, dict):\n            logger.warning(\"Skipping malformed event handler (expected a mapping): %r\", entry)\n            continue\n        handlers.append(entry)\n    return handlers\n\n\ndef _validate_resolved_event(event_name: str, handlers: list[dict[str, Any]]) -> None:\n    \"\"\"Validate a resolved event's handlers, raising a user-facing error.\n\n    Raised for structural problems the user must fix (unknown event name,\n    handler missing a ``command``, or ``command`` not a non-empty string per\n    #17). Malformed-but-skipable entries are already dropped by\n    ``_normalize_handlers``.\n    \"\"\"\n    from .extensions import ValidationError\n\n    if event_name not in CANONICAL_EVENTS:\n        raise ValidationError(\n            f\"Unknown event '{event_name}': must be one of {sorted(CANONICAL_EVENTS)}\"\n        )\n    for handler in handlers:\n        command = handler.get(\"command\")\n        if not isinstance(command, str) or not command.strip():\n            raise ValidationError(\n                f\"Event '{event_name}' handler missing required non-empty 'command' string\"\n            )\n        # C10: matcher must be a string (or absent). A non-string matcher such\n        # as `matcher: []` passes extension validation but later crashes\n        # by_matcher.setdefault(matcher, ...) with TypeError: unhashable type.\n        matcher = handler.get(\"matcher\")\n        if matcher is not None and not isinstance(matcher, str):\n            raise ValidationError(\n                f\"Event '{event_name}' handler has invalid 'matcher': \"\n                \"must be a string\"\n            )\n        timeout = handler.get(\"timeout\")","sourceCodeStart":784,"sourceCodeEnd":820,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/events.py#L784-L820","documentation":"Event handler configuration only accepts the canonical snake_case event names: session_start, pre_tool_use, post_tool_use, session_end, user_prompt_submit, and stop. An unknown key raises ValidationError from `_validate_resolved_event`; in the normal resolve_events flow the exception is caught and the entire user YAML override is abandoned with a warning, keeping built-in/extension events.","triggerScenarios":"Writing `.specify/integration-events.yml` with a CamelCase native hook name such as SessionStart, a hyphenated name such as pre-tool-use, or an unsupported event name under an integration's `events` mapping.","commonSituations":"Copying names from Claude Code's native hooks JSON or an agent vendor's documentation instead of Spec Kit's canonical event vocabulary.","solutions":["Replace the invalid key with one of: session_start, pre_tool_use, post_tool_use, session_end, user_prompt_submit, stop.","Use snake_case exactly; names are case-sensitive.","Re-run integration install/upgrade or event resolution and confirm the warning no longer appears.","If an extension itself declares the bad name, fix its extension.yml events mapping."],"exampleFix":"# .specify/integration-events.yml (before)\nintegrations:\n  claude:\n    events:\n      SessionStart:\n        command: speckit.session-start\n\n# after\nintegrations:\n  claude:\n    events:\n      session_start:\n        command: speckit.session-start","handlingStrategy":"validation","validationCode":"from specify_cli.events import CANONICAL_EVENTS\n\ndef override_events_are_canonical(override: dict) -> bool:\n    integrations = override.get(\"integrations\", {})\n    return all(\n        event in CANONICAL_EVENTS\n        for entry in integrations.values()\n        if isinstance(entry, dict)\n        for event in entry.get(\"events\", {})\n    )","typeGuard":"from typing import Any, TypeGuard\nfrom specify_cli.events import CANONICAL_EVENTS\n\ndef is_canonical_event_name(value: Any) -> TypeGuard[str]:\n    return isinstance(value, str) and value in CANONICAL_EVENTS","tryCatchPattern":"from specify_cli.extensions import ValidationError\n\ntry:\n    _validate_resolved_event(event_name, handlers)\nexcept ValidationError as exc:\n    if str(exc).startswith(\"Unknown event\"):\n        warn_and_ignore_entire_override(event_name)\n    else:\n        raise","preventionTips":["Copy event names from CANONICAL_EVENTS, not agent-native hook names.","Use snake_case and remember names are case-sensitive.","Watch for the 'ignoring entire override' warning after changing integration-events.yml."],"tags":["events","extensions","yaml","configuration","validation"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}