openai / openai/openai-java

fix: clear WorkloadIdentityAuth refresh state after synchronous provider failures

Open
#852 0 comments 0 reactions 0 assignees View on GitHub

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-152 stores refreshInFlight before starting a refresh.
  • WorkloadIdentityAuth.kt:157-174 calls performRefreshAndComplete() or refreshTokenAsync() without catching synchronous exceptions.
  • WorkloadIdentityAuth.kt:178-182 only clears the state from the asynchronous completion callback.
  • SubjectTokenProvider.kt:31-38 allows 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.