aio-libs/aiohttp · error · ValueError
total timeout must be a positive number or None to disable…
Error message
total timeout must be a positive number or None to disable, got 0. Using 0 to disable timeouts is no longer supported, use None instead.
What it means
Raised by ClientTimeout.__post_init__ when the 'total' timeout resolves to exactly 0. Historically, total=0 meant 'disable timeout', but aiohttp now requires None for that; a literal 0 (or all sub-timeouts being 0 with total defaulting to 0) is treated as a misconfiguration and raises ValueError. Note the dataclass first bumps total to the max of itself and any sub-timeout, so total becomes 0 only if total and every sub-timeout are 0/None.
Solutions
- Pass total=None to disable the overall timeout: ClientTimeout(total=None).
- If you want a small timeout, use a positive number: ClientTimeout(total=30).
- Audit config sources (env vars, YAML) that map 'disable' to 0 and remap them to None.
- Upgrade guides: search the codebase for 'ClientTimeout(total=0' and replace.
Example fix
# before timeout = ClientTimeout(total=0) # ValueError # after — disable total timeout explicitly timeout = ClientTimeout(total=None)
Defensive patterns
Strategy: validation
Validate before calling
from aiohttp import ClientTimeout
def safe_timeout(total=None, **kw):
# normalize legacy 0 (disable) to None
if total == 0:
total = None
for k, v in list(kw.items()):
if v == 0:
kw[k] = None
return ClientTimeout(total=total, **kw) Type guard
def is_valid_total(total) -> bool:
return total is None or (isinstance(total, (int, float)) and total > 0) Try / catch
from aiohttp import ClientTimeout
try:
timeout = ClientTimeout(total=cfg_total)
except ValueError as e:
if 'got 0' in str(e):
timeout = ClientTimeout(total=None) # migrate legacy disable semantics
else:
raise Prevention
- Treat 0 as an invalid timeout in all config schemas; use None for 'disabled'.
- Run a grep for 'total=0' across the codebase when upgrading aiohttp.
- Document the None-vs-0 distinction in your config reference.
When it happens
Trigger: Constructing ClientTimeout(total=0); or ClientTimeout(total=0, connect=0, sock_read=0) where all resolve to 0; or passing total=0 implicitly through a session/request. The check in __post_init__ fires at construction time, not at request time.
Common situations: Code ported from an older aiohttp version where total=0 disabled timeouts; config loaded from a file/env where 0 was the legacy 'off' sentinel; copy-paste of a snippet targeting an older API.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Connection timeout to host
- aiohttp only supports http(s) proxies
- compress can not be set if Content-Encoding header is set
- compress must be one of True, False, 'deflate', or 'gzip'
- 1002
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/1e34f2e7566d9b59.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/client_reqrep.py:125
def __post_init__(self) -> None:
# Ensure total is never lower than a more specific timeout, otherwise
# the latter would be silently capped by total and rendered useless.
# total=None means the user explicitly disabled the total timeout.
if self.total is None:
return
object.__setattr__(
self,
"total",
max(
self.total,
self.connect or 0,
self.sock_read or 0,
self.sock_connect or 0,
),
)
if self.total == 0:
raise ValueError(
"total timeout must be a positive number or None to disable, "
"got 0. Using 0 to disable timeouts is no longer supported, "
"use None instead."
)
def _gen_default_accept_encoding() -> str:
encodings = [
"gzip",
"deflate",
]
if HAS_BROTLI:
encodings.append("br")
if HAS_ZSTD:
encodings.append("zstd")
return ", ".join(encodings)
View on GitHub (pinned to d041d4d0fd)