github/spec-kit · error · ValidationError

Event '{event_name}' handler has invalid 'matcher': must be

Error message

Event '{event_name}' handler has invalid 'matcher': must be a string

What it means

Raised while resolving an integration's declared event handlers: a handler entry in an extension's events config supplied a 'matcher' key whose value is neither a string nor null. The check exists (issue C10) because a non-string, unhashable matcher such as `matcher: []` passes earlier validation but later crashes `by_matcher.setdefault(matcher, ...)` with TypeError: unhashable type.

Source

Thrown at src/specify_cli/events.py:816

    """
    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"
                )


def resolve_events(
    integration_key: str,
    integration_config: dict[str, Any] | None,
    project_root: Path,
    parsed_options: dict[str, Any] | None,
) -> ResolvedEvents:
    """Resolve the final event set for an integration.

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Change the matcher value in the handler entry to a plain string, e.g. `matcher: "tool_use"`, or delete the matcher key entirely if no filtering is needed.
  2. Re-run the extension install/refresh; resolve_events re-validates and should pass.
  3. If you intended multiple matchers, split into one handler entry per matcher string.

Example fix

# before (extension.yaml)
events:
  pre_tool_use:
    handlers:
      - command: "./check.sh"
        matcher: [write_file, edit_file]

# after
events:
  pre_tool_use:
    handlers:
      - command: "./check.sh"
        matcher: "write_file|edit_file"
Defensive patterns

Strategy: validation

Validate before calling

def valid_handler(handler: dict) -> bool:
    m = handler.get("matcher")
    return m is None or isinstance(m, str)

Try / catch

from specify_cli.extensions import ValidationError
try:
    resolve_events(key, config, project_root, options)
except ValidationError as e:
    if "matcher" in str(e):
        fix_manifest_and_retry()  # matcher must be a str or absent
    raise

Prevention

When it happens

Trigger: An extension declares `events:` for an integration where a handler entry is a mapping containing `matcher: []`, `matcher: 123`, or `matcher: {a: b}`; resolve_events() -> handler validation loop hits the non-string matcher and raises ValidationError before any config is written.

Common situations: Authoring an extension manifest (extension.yaml) with a YAML list or number for matcher instead of a quoted string; copy-pasting a matcher syntax from a different tool that accepts arrays/patterns; YAML unquoted values parsing to non-string types.

Related errors


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