github/spec-kit · error · ValidationError

Event '{event_name}' has invalid 'timeout': must be a positi

Error message

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

What it means

Manifest-level timeout validation in validate_events(): an event config's `timeout`, when present, must be a positive integer and not a boolean. Same rationale as the resolver-side check — bools are ints in Python and are explicitly excluded.

Source

Thrown at src/specify_cli/events.py:1811

                raise ValidationError(
                    f"Event '{event_name}' missing required 'command' string"
                )
            if event_name not in CANONICAL_EVENTS:
                raise ValidationError(
                    f"Unknown event '{event_name}': "
                    f"must be one of {sorted(CANONICAL_EVENTS)}"
                )
            # C10: matcher must be a string (or absent). A non-string matcher
            # such as `matcher: []` would later crash by_matcher.setdefault.
            matcher = event_config.get("matcher")
            if matcher is not None and not isinstance(matcher, str):
                raise ValidationError(
                    f"Event '{event_name}' has invalid 'matcher': must be a string"
                )
            timeout = event_config.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}' has invalid 'timeout': must be a positive integer"
                    )


def has_events(data: dict[str, Any]) -> bool:
    """Return True if ``events`` is present and non-empty."""
    return bool(data.get("events"))


# -- Helper merging functions ----------------------------------------------

def _toml_quote(value: str) -> str:
    """Render *value* as a TOML basic string via the shared escaper."""
    from ._toml_string import escape_toml_basic
    return escape_toml_basic(value)


def _build_opencode_plugin(

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Use a bare positive integer: `timeout: 60`.
  2. Drop the key entirely to accept the default.
  3. Verify no quotes and no decimal point on the value.

Example fix

# before
events:
  pre_tool_use:
    command: "./hook.sh"
    timeout: 60.0

# after
events:
  pre_tool_use:
    command: "./hook.sh"
    timeout: 60
Defensive patterns

Strategy: validation

Validate before calling

t = event_config.get("timeout")
if t is not None and (not isinstance(t, int) or isinstance(t, bool) or t <= 0):
    event_config.pop("timeout")  # or int(t) after asserting integral

Type guard

def timeout_ok(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 "invalid 'timeout'" in str(e):
        event_config["timeout"] = int(float(raw)) if str(raw).replace('.','',1).isdigit() else default

Prevention

When it happens

Trigger: Manifest event config has `timeout: 2.5`, `timeout: "60"`, `timeout: 0`, `timeout: -5`, or `timeout: false`; validate_events raises during manifest validation.

Common situations: Float seconds from another tool's config, quoted numbers, zero meaning 'no timeout' in the author's mind, YAML boolean coercion (`timeout: yes`).

Understand the failure class

Related errors


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