Description
RetryingHttpClient does not clamp negative Retry-After values to zero before passing them to the configured Sleeper. A server-provided past HTTP date, negative numeric delay, or negative millisecond delay can therefore make an otherwise retryable request fail instead of retrying immediately.
Reproduction
Configure a retrying client with the default sleeper and return a retryable response containing a past HTTP-date:
// The date is already in the past when the response is handled.
Retry-After: Wed, 21 Oct 2015 07:28:00 GMT
Or return either of these headers:
Retry-After: -1
Retry-After-Ms: -1
Then make a request that receives a retryable 5xx/429 response.
Code reference
openai-java-core/src/main/kotlin/com/openai/core/http/RetryingHttpClient.kt:197-218 parses numeric and HTTP-date values and returns Duration.ofNanos(retryAfterNanos.toLong()) without clamping.
RetryingHttpClient.kt:72 passes the duration to synchronous Sleeper.sleep().
RetryingHttpClient.kt:121 passes the duration to asynchronous Sleeper.sleepAsync().
openai-java-core/src/main/kotlin/com/openai/core/DefaultSleeper.kt:13-22 calls Thread.sleep(duration.toMillis()) or schedules a TimerTask directly.
Expected behavior
Negative retry delays should be treated as zero, so the retry proceeds immediately. This is the usual interpretation for an HTTP-date that has already elapsed and avoids making clock skew or malformed negative delay values fatal.
Actual behavior
The synchronous default sleeper throws IllegalArgumentException from Thread.sleep for a negative duration. The asynchronous default sleeper can throw from Timer.schedule, causing the retry future to fail instead of issuing the next attempt.
Why it matters
Retry-After HTTP dates are especially vulnerable to client/server clock skew and network transit time. A valid retryable response can therefore become a non-retryable SDK failure, affecting both synchronous and asynchronous callers.
Suggested fix
Clamp parsed retry durations to Duration.ZERO before returning them, and add sync/async regression coverage for past dates and negative numeric headers.
Description
RetryingHttpClientdoes not clamp negativeRetry-Aftervalues to zero before passing them to the configuredSleeper. A server-provided past HTTP date, negative numeric delay, or negative millisecond delay can therefore make an otherwise retryable request fail instead of retrying immediately.Reproduction
Configure a retrying client with the default sleeper and return a retryable response containing a past HTTP-date:
Or return either of these headers:
Then make a request that receives a retryable 5xx/429 response.
Code reference
openai-java-core/src/main/kotlin/com/openai/core/http/RetryingHttpClient.kt:197-218parses numeric and HTTP-date values and returnsDuration.ofNanos(retryAfterNanos.toLong())without clamping.RetryingHttpClient.kt:72passes the duration to synchronousSleeper.sleep().RetryingHttpClient.kt:121passes the duration to asynchronousSleeper.sleepAsync().openai-java-core/src/main/kotlin/com/openai/core/DefaultSleeper.kt:13-22callsThread.sleep(duration.toMillis())or schedules aTimerTaskdirectly.Expected behavior
Negative retry delays should be treated as zero, so the retry proceeds immediately. This is the usual interpretation for an HTTP-date that has already elapsed and avoids making clock skew or malformed negative delay values fatal.
Actual behavior
The synchronous default sleeper throws
IllegalArgumentExceptionfromThread.sleepfor a negative duration. The asynchronous default sleeper can throw fromTimer.schedule, causing the retry future to fail instead of issuing the next attempt.Why it matters
Retry-After HTTP dates are especially vulnerable to client/server clock skew and network transit time. A valid retryable response can therefore become a non-retryable SDK failure, affecting both synchronous and asynchronous callers.
Suggested fix
Clamp parsed retry durations to
Duration.ZERObefore returning them, and add sync/async regression coverage for past dates and negative numeric headers.