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
- 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.
- Re-run the extension install/refresh; resolve_events re-validates and should pass.
- 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
- Keep matcher as a single string; use the integration's own alternation syntax instead of a list.
- Lint extension manifests in CI with yaml.safe_load plus isinstance checks before running specify.
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
- Unknown event '{event_name}': must be one of {sorted(CANONIC
- Event '{event_name}' handler missing required non-empty 'com
- Event '{event_name}' handler has invalid 'timeout': must be
- Invalid events: expected a mapping
- Invalid event '{event_name}': expected a mapping
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/d4c69c65ba30efbe.
Report an issue: GitHub.