{"record":{"id":"64ab0e48615e4134","repo":"psf/requests","slug":"invalid-timeout-timeout-pass-a-connect-read","errorCode":null,"errorMessage":"Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, or a single float to set both timeouts to the same value.","messagePattern":"Invalid timeout (.+?)\\. Pass a \\(connect, read\\) timeout tuple, or a single float to set both timeouts to the same value\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/requests/adapters.py","lineNumber":686,"sourceCode":"        self.cert_verify(conn, request.url, verify, cert)\n        url = self.request_url(request, proxies)\n        self.add_headers(\n            request,\n            stream=stream,\n            timeout=timeout,\n            verify=verify,\n            cert=cert,\n            proxies=proxies,\n        )\n\n        chunked = not (request.body is None or \"Content-Length\" in request.headers)\n\n        if isinstance(timeout, tuple):\n            try:\n                connect, read = timeout\n                resolved_timeout = TimeoutSauce(connect=connect, read=read)\n            except ValueError:\n                raise ValueError(\n                    f\"Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, \"\n                    f\"or a single float to set both timeouts to the same value.\"\n                )\n        elif isinstance(timeout, TimeoutSauce):\n            resolved_timeout = timeout\n        else:\n            resolved_timeout = TimeoutSauce(connect=timeout, read=timeout)\n\n        try:\n            resp = conn.urlopen(\n                method=request.method,\n                url=url,\n                body=request.body,  # type: ignore[arg-type]  # urllib3 stubs don't accept Iterable[bytes | str]\n                headers=request.headers,  # type: ignore[arg-type]  # urllib3#3072\n                redirect=False,\n                assert_same_host=False,\n                preload_content=False,\n                decode_content=False,","sourceCodeStart":668,"sourceCodeEnd":704,"githubUrl":"https://github.com/psf/requests/blob/8068356288978c4f54661ae6f95afe0e0831885e/src/requests/adapters.py#L668-L704","documentation":"Raised when the timeout argument is a tuple that does not unpack into exactly two values (connect, read). The code calls connect, read = timeout inside a try; a tuple of length != 2 raises ValueError, which is caught and re-raised with this descriptive message. Non-tuple timeouts (float, int, TimeoutSauce) bypass this branch entirely.","triggerScenarios":"Triggered by passing timeout=(5,) (one element), timeout=(1, 2, 3) (three elements), or any tuple/list whose length is not 2, to request/get/post/send or a Session. The error occurs before any network call, during timeout resolution in send.","commonSituations":"Happens when callers build timeouts dynamically and accidentally produce a 1- or 3-tuple, when copying a timeout from config that was meant to be a single float but got wrapped, or when using a list instead of a 2-tuple. Easy to introduce via f-string/config-driven timeout assembly.","solutions":["Pass a single float for equal connect/read timeouts: timeout=5.0.","Pass exactly a 2-tuple: timeout=(connect, read).","Validate dynamic timeouts before sending: assert len(t) == 2 when t is a tuple/list.","Normalize config-driven values through a helper that always returns a float or a 2-tuple.","Add a unit test that exercises the timeout builder with edge inputs."],"exampleFix":"# before\nrequests.get(url, timeout=(5,))  # -> ValueError: Invalid timeout\n\n# after\nrequests.get(url, timeout=(5, 30))  # (connect, read)","handlingStrategy":"type-guard","validationCode":"def normalize_timeout(t):\n    \"\"\"Always return a float or a 2-tuple (connect, read).\"\"\"\n    if isinstance(t, (int, float)):\n        return float(t)\n    if isinstance(t, tuple):\n        if len(t) != 2:\n            raise ValueError(\n                f\"timeout tuple must be (connect, read), got {t!r}\"\n            )\n        return (float(t[0]), float(t[1]))\n    # assume it's already a urllib3 Timeout\n    return t\n\nrequests.get(url, timeout=normalize_timeout(config[\"timeout\"]))","typeGuard":"def is_valid_timeout(t) -> bool:\n    if isinstance(t, (int, float)):\n        return t >= 0\n    if isinstance(t, tuple):\n        return (\n            len(t) == 2\n            and all(isinstance(x, (int, float)) and x >= 0 for x in t)\n        )\n    from urllib3.util import Timeout\n    return isinstance(t, Timeout)","tryCatchPattern":"try:\n    resp = session.get(url, timeout=timeout)\nexcept ValueError as e:\n    if \"Invalid timeout\" in str(e):\n        # fall back to a safe default and retry once\n        resp = session.get(url, timeout=30.0)\n    else:\n        raise","preventionTips":["Build timeouts from typed config, not ad-hoc tuples.\nDocument the (connect, read) convention anywhere timeouts are configured.\nUnit-test your timeout builder with 1-, 2-, and 3-element inputs.\nPrefer a single float unless connect and read genuinely differ."],"tags":["timeout","validation","config","argument-error"],"backgroundTag":null,"analyzedSha":"8068356288978c4f54661ae6f95afe0e0831885e","analyzedAt":"2026-08-11T20:11:09.238Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}