{"record":{"id":"97f13c304134d272","repo":"boto/boto3","slug":"max-retries-exceeded","errorCode":null,"errorMessage":"Max Retries Exceeded","messagePattern":"Max Retries Exceeded","errorType":"exception","errorClass":"RetriesExceededError","httpStatus":null,"severity":"error","filePath":"boto3/s3/transfer.py","lineNumber":491,"sourceCode":"        \"\"\"\n        if isinstance(filename, PathLike):\n            filename = fspath(filename)\n        if not isinstance(filename, str):\n            raise ValueError('Filename must be a string or a path-like object')\n\n        subscribers = self._get_subscribers(callback)\n        future = self._manager.download(\n            bucket, key, filename, extra_args, subscribers\n        )\n        try:\n            future.result()\n        # This is for backwards compatibility where when retries are\n        # exceeded we need to throw the same error from boto3 instead of\n        # s3transfer's built in RetriesExceededError as current users are\n        # catching the boto3 one instead of the s3transfer exception to do\n        # their own retries.\n        except S3TransferRetriesExceededError as e:\n            raise RetriesExceededError(e.last_exception)\n\n    def _get_subscribers(self, callback):\n        if not callback:\n            return None\n        return [ProgressCallbackInvoker(callback)]\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, *args):\n        self._manager.__exit__(*args)\n\n\nclass ProgressCallbackInvoker(BaseSubscriber):\n    \"\"\"A back-compat wrapper to invoke a provided callback via a subscriber\n\n    :param callback: A callable that takes a single positional argument for\n        how many bytes were transferred.","sourceCodeStart":473,"sourceCodeEnd":509,"githubUrl":"https://github.com/boto/boto3/blob/6e10b029c1326a437932a8b24f38af69fd986e15/boto3/s3/transfer.py#L473-L509","documentation":"`RetriesExceededError` is raised by `S3Transfer.download_file` when the underlying s3transfer exhausts all retry attempts (each chunk/part download keeps failing). It wraps `S3TransferRetriesExceededError` for backwards compatibility, since users historically caught boto3's exception. The inner `last_exception` carries the cause of the final failed attempt. It usually points at persistent network problems or a consistently failing request (e.g. permission denied on every retry), not transient noise.","triggerScenarios":"A `download_file` call where every attempt to fetch the object (or one of its parts in a multipart download) fails: repeated timeouts, connection resets, persistent `403 AccessDenied`, or `404 NoSuchKey` on an object that never appears during the retry window.","commonSituations":"Unstable/slow network (mobile, congested NAT), restrictive firewall/proxy dropping large ranged GETs, IAM permissions failing consistently, an object that was deleted mid-download, or the default retry budget being too small for a flaky link combined with a large file.","solutions":["Inspect `e.last_exception` to see the real failure (ClientError code or socket error) — fixing the root cause beats raising the retry cap.","Increase the retry budget: `TransferConfig(max_request_concurrency=..., max_concurrency=...)` and, for the client, set `Config(retries={'max_attempts': 10, 'mode': 'adaptive'})`.","Reduce per-request size to lower the chance any single part fails: `TransferConfig(multipart_threshold=..., multipart_chunksize=...)`.","For transient network issues, wrap the call in an outer retry with backoff (e.g. `tenacity`), and verify connectivity to `*.s3.<region>.amazonaws.com`."],"exampleFix":"# before\nclient.download_file('bucket', 'big', '/tmp/big')  # RetriesExceededError\n\n# after: diagnose + expand budget\nfrom botocore.config import Config\nclient = boto3.client('s3', config=Config(retries={'max_attempts': 10, 'mode': 'adaptive'}))\nfrom boto3.s3.transfer import TransferConfig\nclient.download_file('bucket', 'big', '/tmp/big', Config=TransferConfig(max_concurrency=20))","handlingStrategy":"retry","validationCode":"from botocore.config import Config\nfrom boto3.s3.transfer import TransferConfig\n\ns3 = boto3.client('s3', config=Config(retries={'max_attempts': 10, 'mode': 'adaptive'}))\ntcfg = TransferConfig(max_concurrency=20)\n# inspect last_exception after a failure to decide if a retry is worthwhile","typeGuard":"from boto3.exceptions import RetriesExceededError\ndef is_retries_exceeded(e) -> bool:\n    return isinstance(e, RetriesExceededError)","tryCatchPattern":"import time\nfrom boto3.exceptions import RetriesExceededError\nattempt = 0\nwhile True:\n    try:\n        client.download_file(bucket, key, path, Config=tcfg)\n        break\n    except RetriesExceededError as e:\n        attempt += 1\n        if attempt > 3:\n            raise\n        last = getattr(e, 'last_exception', None)\n        log.warning('download retry %d, last=%r', attempt, last)\n        time.sleep(2 ** attempt)","preventionTips":["Use botocore Config retries mode='adaptive' for backoff on throttling.","Inspect last_exception before assuming retries will ever succeed.","Verify network egress to S3 and IAM perms before retrying."],"tags":["s3","download","retries","network","transfer"],"backgroundTag":null,"analyzedSha":"6e10b029c1326a437932a8b24f38af69fd986e15","analyzedAt":"2026-08-11T20:52:47.213Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}