fix: clear WorkloadIdentityAuth refresh state after synchronous provider failures
Nobody has claimed this yet.
- Dominant language
- Kotlin
- Stars
- 1.5k
- Forks
- 264
- Avg merge
- 9h 46m
- Merged PRs (30d)
- 96
Description
Description
WorkloadIdentityAuth.getTokenAsync() can permanently poison its token-refresh state when a custom SubjectTokenProvider.getTokenAsync() throws synchronously instead of returning an exceptionally completed future.
The method stores a new CompletableFuture in refreshInFlight before calling refreshTokenAsync(). If the provider throws synchronously, the exception escapes before finishRefresh() is registered, so refreshInFlight is never cleared or completed.
Reproduction
Implement a provider whose asynchronous method throws directly:
val provider = object : SubjectTokenProvider {
override fun tokenType() = SubjectTokenType.JWT
override fun getToken(httpClient: HttpClient, jsonMapper: JsonMapper): String =
"subject-token"
override fun getTokenAsync(
httpClient: HttpClient,
jsonMapper: JsonMapper,
): CompletableFuture<String> {
throw IllegalStateException("provider failed")
}
}
Use it in a WorkloadIdentityAuth and call:
assertThrows<IllegalStateException> { auth.getTokenAsync() }
val second = auth.getTokenAsync()
assertTimeoutPreemptively(Duration.ofSeconds(1)) { second.join() }
Code reference
openai-java-core/src/main/kotlin/com/openai/auth/WorkloadIdentityAuth.kt:134-152storesrefreshInFlightbefore starting a refresh.WorkloadIdentityAuth.kt:157-174callsperformRefreshAndComplete()orrefreshTokenAsync()without catching synchronous exceptions.WorkloadIdentityAuth.kt:178-182only clears the state from the asynchronous completion callback.SubjectTokenProvider.kt:31-38allows custom implementations of the async provider method.
Expected behavior
A synchronous provider exception should be converted into a failed future, and refreshInFlight should be cleared so later calls can retry or fail normally.
Actual behavior
The first call throws synchronously. Subsequent callers observe the abandoned refreshInFlight future and wait forever.
The same issue can occur on the background refresh path when refreshTokenAsync() throws before registering its completion callback.
Why it matters
A transient or poorly behaved custom credential provider can permanently hang all future asynchronous authentication attempts in a long-lived client. This is especially problematic because the failure is not limited to the request that encountered the provider exception.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with openai-java-core/src/main/kotlin/com/openai/auth/WorkloadIdentityAuth.kt:134-182 and then inspect SubjectTokenProvider.kt:31-38. Trace both the direct and background refresh paths, focusing on synchronous exceptions before completion callbacks are registered. Done means synchronous provider failures produce failed futures, refreshInFlight is cleared, and later calls do not wait indefinitely.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- kotlin
- Domain
- authentication
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100