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_code

View on GitHub (pinned to b5b53acc57)

Solutions

  1. Pass a positive timeout such as get_zep_client(timeout=30.0), or omit the argument to use the built-in default.
  2. If the timeout is configurable, validate it as a positive number with a fallback default before calling.
  3. 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

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

Related errors


AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14). Data as JSON: /api/errors/b8a2637eaeba5048. Report an issue: GitHub.