666ghj/MiroFish · error · ValueError
Zep request timeout must be greater than 0
Error message
Zep request timeout must be greater than 0
What it means
Raised by get_zep_client in backend/app/utils/zep.py when the resolved HTTP timeout is <= 0. The timeout is float(timeout) if the argument is not None, otherwise ZEP_HTTP_REQUEST_TIMEOUT_SECONDS — so a numeric string like "0" also lands here after float conversion, as does a negative constant misconfigured in settings. The client is constructed with this exact timeout, so a non-positive value is rejected before _cached_zep_client.
Source
Thrown at backend/app/utils/zep.py:82
def get_zep_client(api_key: str | None = None, timeout: float | None = None) -> Zep:
"""Return a process-shared, explicitly configured Zep Cloud client."""
# zep-cloud gives ZEP_API_URL precedence even when base_url is explicit.
# Reject it so this Cloud-only integration cannot silently target a
# self-hosted or compatibility endpoint.
if os.environ.get("ZEP_API_URL"):
raise ValueError("ZEP_API_URL is unsupported; unset it to use Zep Cloud")
normalized_key = (api_key or Config.ZEP_API_KEY or "").strip()
if not normalized_key:
raise ValueError("ZEP_API_KEY 未配置")
request_timeout = float(
timeout if timeout is not None else ZEP_HTTP_REQUEST_TIMEOUT_SECONDS
)
if request_timeout <= 0:
raise ValueError("Zep request timeout must be greater than 0")
return _cached_zep_client(normalized_key, request_timeout)
def clear_zep_client_cache() -> None:
"""Clear cached clients. Intended for tests and controlled reconfiguration."""
_cached_zep_client.cache_clear()
def is_retryable_zep_error(error: BaseException) -> bool:
"""Return whether a failed *read* is safe and useful to retry."""
if isinstance(error, (httpx.TimeoutException, httpx.TransportError)):
return True
if isinstance(error, (ConnectionError, TimeoutError, OSError)):
return True
if isinstance(error, ZepApiError):
status_code = error.status_codeView on GitHub (pinned to b5b53acc57)
Solutions
- Pass a positive timeout such as get_zep_client(timeout=30.0), or omit the argument to use the built-in default.
- If the timeout is configurable, validate it as a positive number with a fallback default before calling.
- Use None (not 0 or negatives) to request the library default.
Example fix
# before client = get_zep_client(api_key=key, timeout=cfg_timeout) # cfg_timeout = 0 # after timeout = cfg_timeout if cfg_timeout and cfg_timeout > 0 else None client = get_zep_client(api_key=key, timeout=timeout)
Defensive patterns
Strategy: validation
Validate before calling
def safe_timeout(value: Any) -> float | None:
if value is None:
return None # library default
t = float(value)
return t if t > 0 else None Type guard
def is_positive_timeout(value: Any) -> TypeGuard[float]:
try:
return float(value) > 0
except (TypeError, ValueError):
return False Try / catch
try:
client = get_zep_client(timeout=t)
except ValueError as e:
if "timeout" in str(e):
client = get_zep_client() # library default
else:
raise Prevention
- Validate configurable timeouts as positive numbers with a documented default.
- Use None (not 0) to request the library default timeout.
- Unit-test the config path that produces the timeout value.
When it happens
Trigger: Calling get_zep_client(timeout=0), timeout=-1, or timeout="0"; or the ZEP_HTTP_REQUEST_TIMEOUT_SECONDS default being set to 0 in the settings module.
Common situations: Using 0 to mean 'system default timeout' (this API has no such sentinel — pass None instead); a timeout computed from config where an unset field defaults to 0; tests passing timeout=0 accidentally.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- ZEP_API_KEY 未配置
- graph_id is required
- At least one text chunk is required
- batch_size must be between 1 and 350
- A Zep batch cannot contain more than 50,000 items
AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14).
Data as JSON: /api/errors/b8a2637eaeba5048.
Report an issue: GitHub.