Finite retry exhaustion returns None instead of raising
Problem
retry_on_specific_exceptions() silently returns None when every finite retry attempt raises a configured exception.
Reproduction
class RetryableError(Exception):
pass
calls = 0
@retry_on_specific_exceptions(
[RetryableError], max_retries=3, backoff_time=0
)
def always_fails():
global calls
calls += 1
raise RetryableError("still failing")
result = always_fails()
print(calls, result)Observed output:
3 NoneThe final RetryableError should be raised instead.
Cause
The wrapper catches every configured exception and increments attempt. Once attempt == max_retries, the loop condition becomes false and execution falls off the end of the wrapper without a return or raise statement. Python therefore returns None.
Impact
The documented finite-retry configuration converts a provider or network failure into an apparent successful None result. Callers can then fail later with an unrelated error or incorrectly accept the missing response. Infinite-retry in-tree callers do not reach this path, but the decorator documentation explicitly recommends setting max_retries.
Expected behavior
Preserving the current attempt count, the final configured exception should propagate when the finite limit is reached. Backoff callbacks and sleeping should occur only when another attempt will actually be made.
Proposed resolution
After a retryable exception, increment the attempt counter and use a bare raise when the finite limit has been exhausted. Add regression coverage for eventual success and final failure, including attempt and sleep counts.
Source: EleutherAI/lm-evaluation-harness