{"record":{"id":"f95b909a86e2484a","repo":"github/spec-kit","slug":"event-event-name-handler-missing-required-non","errorCode":null,"errorMessage":"Event '{event_name}' handler missing required non-empty 'command' string","messagePattern":"Event '(.+?)' handler missing required non-empty 'command' string","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"warning","filePath":"src/specify_cli/events.py","lineNumber":808,"sourceCode":"\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\")\n        if timeout is not None:\n            if not isinstance(timeout, int) or isinstance(timeout, bool) or timeout <= 0:\n                raise ValidationError(\n                    f\"Event '{event_name}' handler has invalid 'timeout': must be a positive integer\"\n                )\n","sourceCodeStart":790,"sourceCodeEnd":826,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/events.py#L790-L826","documentation":"Every event handler mapping must contain a `command` that is a non-empty string after stripping whitespace. A missing, null, numeric, list-valued, empty, or whitespace-only command raises ValidationError. resolve_events catches this for user overrides and ignores the entire override with a warning, so hooks from lower layers remain active.","triggerScenarios":"A handler in `.specify/integration-events.yml` omits `command`, writes `command: \"\"`, uses a script/matcher key without command, or sets a non-string command value.","commonSituations":"YAML indentation places `command` under the wrong mapping, a template variable was never filled in, or the user assumes `script:`/`template:` is accepted instead of `command:`.","solutions":["Find the event named in the error and ensure each handler mapping has `command: <non-empty-string>`.","Fix indentation so command belongs to the handler mapping, not the event key.","Use the canonical command/template name expected by the integration, for example `speckit.session-start`.","Remove placeholder handlers until a real command is available."],"exampleFix":"# before\nintegrations:\n  claude:\n    events:\n      session_start:\n        matcher: \"*\"\n\n# after\nintegrations:\n  claude:\n    events:\n      session_start:\n        command: speckit.session-start\n        matcher: \"*\"","handlingStrategy":"validation","validationCode":"def handlers_have_nonempty_commands(handlers: list[dict]) -> bool:\n    for handler in handlers:\n        command = handler.get(\"command\")\n        if not isinstance(command, str) or not command.strip():\n            return False\n    return True","typeGuard":"from typing import Any, TypeGuard\n\ndef is_valid_event_handler(value: Any) -> TypeGuard[dict[str, Any]]:\n    command = value.get(\"command\") if isinstance(value, dict) else None\n    return isinstance(command, str) and bool(command.strip())","tryCatchPattern":"from specify_cli.extensions import ValidationError\n\ntry:\n    _validate_resolved_event(event_name, handlers)\nexcept ValidationError as exc:\n    if \"missing required non-empty 'command'\" in str(exc):\n        warn_and_ignore_entire_override(event_name)\n    else:\n        raise","preventionTips":["Template integration-events.yml from a known-good example and fill every command.","Validate event YAML in editor/CI before integration upgrade.","Remember a single invalid handler invalidates the whole integration override."],"tags":["events","extensions","yaml","configuration","validation"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}