Finite retry exhaustion returns None instead of raising

Author: sdivyanshu90Created Sep 16, 2026Updated Sep 16, 2026

Problem

retry_on_specific_exceptions() silently returns None when every finite retry attempt raises a configured exception.

Reproduction

python
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 None

The 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