github/spec-kit · warning · ValidationError

Event '{event_name}' handler missing required non-empty 'com

Error message

Event '{event_name}' handler missing required non-empty 'command' string

What it means

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.

Source

Thrown at src/specify_cli/events.py:808

def _validate_resolved_event(event_name: str, handlers: list[dict[str, Any]]) -> None:
    """Validate a resolved event's handlers, raising a user-facing error.

    Raised for structural problems the user must fix (unknown event name,
    handler missing a ``command``, or ``command`` not a non-empty string per
    #17). Malformed-but-skipable entries are already dropped by
    ``_normalize_handlers``.
    """
    from .extensions import ValidationError

    if event_name not in CANONICAL_EVENTS:
        raise ValidationError(
            f"Unknown event '{event_name}': must be one of {sorted(CANONICAL_EVENTS)}"
        )
    for handler in handlers:
        command = handler.get("command")
        if not isinstance(command, str) or not command.strip():
            raise ValidationError(
                f"Event '{event_name}' handler missing required non-empty 'command' string"
            )
        # C10: matcher must be a string (or absent). A non-string matcher such
        # as `matcher: []` passes extension validation but later crashes
        # by_matcher.setdefault(matcher, ...) with TypeError: unhashable type.
        matcher = handler.get("matcher")
        if matcher is not None and not isinstance(matcher, str):
            raise ValidationError(
                f"Event '{event_name}' handler has invalid 'matcher': "
                "must be a string"
            )
        timeout = handler.get("timeout")
        if timeout is not None:
            if not isinstance(timeout, int) or isinstance(timeout, bool) or timeout <= 0:
                raise ValidationError(
                    f"Event '{event_name}' handler has invalid 'timeout': must be a positive integer"
                )

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Find the event named in the error and ensure each handler mapping has `command: <non-empty-string>`.
  2. Fix indentation so command belongs to the handler mapping, not the event key.
  3. Use the canonical command/template name expected by the integration, for example `speckit.session-start`.
  4. Remove placeholder handlers until a real command is available.

Example fix

# before
integrations:
  claude:
    events:
      session_start:
        matcher: "*"

# after
integrations:
  claude:
    events:
      session_start:
        command: speckit.session-start
        matcher: "*"
Defensive patterns

Strategy: validation

Validate before calling

def handlers_have_nonempty_commands(handlers: list[dict]) -> bool:
    for handler in handlers:
        command = handler.get("command")
        if not isinstance(command, str) or not command.strip():
            return False
    return True

Type guard

from typing import Any, TypeGuard

def is_valid_event_handler(value: Any) -> TypeGuard[dict[str, Any]]:
    command = value.get("command") if isinstance(value, dict) else None
    return isinstance(command, str) and bool(command.strip())

Try / catch

from specify_cli.extensions import ValidationError

try:
    _validate_resolved_event(event_name, handlers)
except ValidationError as exc:
    if "missing required non-empty 'command'" in str(exc):
        warn_and_ignore_entire_override(event_name)
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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:`.

Related errors


AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14). Data as JSON: /api/errors/f95b909a86e2484a. Report an issue: GitHub.