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
- Use a bare positive integer: `timeout: 60`.
- Drop the key entirely to accept the default.
- 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
- Bare integer literals only; no quotes, no decimals, no on/off words.
- Omit the key rather than setting 0 to mean 'default'.
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Event '{event_name}' handler has invalid 'timeout': must be
- Invalid events: expected a mapping
- Invalid event '{event_name}': expected a mapping
- Event '{event_name}' missing required 'command' string
- Event '{event_name}' has invalid 'matcher': must be a string
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/36eca8e324e00c65.
Report an issue: GitHub.