github/spec-kit · error · ValidationError

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

Error message

Event '{event_name}' handler has invalid 'timeout': must be a positive integer

What it means

Raised while resolving an integration's event handlers: a handler entry supplied a 'timeout' key that is not a positive integer. Booleans are explicitly rejected (`isinstance(timeout, bool)` guard) because bool is a subclass of int in Python, and zero/negative values would make no sense for a process timeout.

Source

Thrown at src/specify_cli/events.py:823

    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.

    Returns a mapping of canonical event name → ordered list of handler
    configs. Layers (lowest → highest precedence):

    1. CLI gate ``--events false`` → empty map (caller still removes prior
       native hooks; see ``install_integration_events``).
    2. Built-in defaults from ``integration_config["events"]`` (single-config

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Set timeout to a bare positive integer, e.g. `timeout: 30`.
  2. Remove the timeout key to use the integration's default timeout.
  3. Check for accidental YAML type coercion (quotes make a string; `.0` makes a float; on/off style words make booleans).

Example fix

# before
handlers:
  - command: "./check.sh"
    timeout: "30"

# after
handlers:
  - command: "./check.sh"
    timeout: 30
Defensive patterns

Strategy: validation

Validate before calling

def valid_timeout(t) -> bool:
    return t is None or (isinstance(t, int) and not isinstance(t, bool) and t > 0)

Try / catch

except ValidationError as e:
    if "timeout" in str(e):
        # normalize: coerce numeric strings/floats to int or drop the key
        handler.pop("timeout", None)

Prevention

When it happens

Trigger: A handler entry in an extension's events config contains `timeout: 10.5`, `timeout: "30"`, `timeout: -1`, `timeout: 0`, or `timeout: true`; resolve_events() handler validation raises ValidationError before native config is rendered.

Common situations: Writing a YAML float (`timeout: 30.0`), quoting the number (`timeout: "30"`), or using `timeout: true` expecting it to mean enabled; porting timeout values from another format that stores seconds as strings.

Understand the failure class

Related errors


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