From eaa076913ab1f7597f0932d6286d573295778811 Mon Sep 17 00:00:00 2001 From: dpiet-oai <326640584+dpiet-oai@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:26:17 +0000 Subject: [PATCH 1/2] fix(auth): close transport after workload cleanup failure Castiron-Internal-PR: https://github.com/openai/openai-java-internal/pull/181 Castiron-Source-SHA: 99d7b3139cef86814ebf1f3f98f1965333cbae2e Castiron-Public-Base-SHA: 362468ef61b3605dda827c2b0492258eaf9edb50 --- .castiron.stats.yml | 14 ++-- .../core/http/WorkloadIdentityHttpClient.kt | 13 +++- .../http/WorkloadIdentityHttpClientTest.kt | 75 +++++++++++++++++++ 3 files changed, 94 insertions(+), 8 deletions(-) diff --git a/.castiron.stats.yml b/.castiron.stats.yml index f2ac0d2ec..2b748ad83 100644 --- a/.castiron.stats.yml +++ b/.castiron.stats.yml @@ -1,8 +1,8 @@ schema_version: 1 -generation_id: 4a45859f-2ddc-4ae8-8d48-7d66ac4e16fe -openapi_spec_hash: 8aa23d19137079c724365bd6cadd0858 -openapi_transformed_spec_hash: d591fedadfdd68837534b2574782bf81 -config_hash: 70e6e763ed8ac8c4038de193a96c771d -codegen_sha: 460e3ec26544481b476774331018f126c57454d6 -codegen_hash: c35eb5de9f3bd898d3bf8745619d12d9ffcc5bc4e3ca332d978e6b42813243fd -public_codegen_sha: 0f5e1cc6093a03afec0cc7ad21d18921436e173f +generation_id: 7b64f44d-477d-4498-831f-1eeae1ce8a60 +openapi_spec_hash: 982e032cd9bc8921825601fbb36a8a92 +openapi_transformed_spec_hash: 7554bcd8a14262861882411fc4426386 +config_hash: ac46b7ace0a09008785717fbff2f1d19 +codegen_sha: 025c61e33941e37fa48d15f37af364f31e03fece +codegen_hash: 88003cad1add3c75fc7037929aa52ec1d4113b49586efe282ac37e8aa22f37ea +public_codegen_sha: ec0f52cdb65b936ec1850ee6041dae200fe5b126 diff --git a/openai-java-core/src/main/kotlin/com/openai/core/http/WorkloadIdentityHttpClient.kt b/openai-java-core/src/main/kotlin/com/openai/core/http/WorkloadIdentityHttpClient.kt index 8a4b62443..58a7a8fbb 100644 --- a/openai-java-core/src/main/kotlin/com/openai/core/http/WorkloadIdentityHttpClient.kt +++ b/openai-java-core/src/main/kotlin/com/openai/core/http/WorkloadIdentityHttpClient.kt @@ -58,7 +58,18 @@ internal class WorkloadIdentityHttpClient( } override fun close() { - workloadIdentityAuth?.close() + try { + workloadIdentityAuth?.close() + } catch (authFailure: Throwable) { + try { + delegate.close() + } catch (delegateFailure: Throwable) { + if (delegateFailure !== authFailure) { + authFailure.addSuppressed(delegateFailure) + } + } + throw authFailure + } delegate.close() } } diff --git a/openai-java-core/src/test/kotlin/com/openai/core/http/WorkloadIdentityHttpClientTest.kt b/openai-java-core/src/test/kotlin/com/openai/core/http/WorkloadIdentityHttpClientTest.kt index 2efd06e13..a3d720b16 100644 --- a/openai-java-core/src/test/kotlin/com/openai/core/http/WorkloadIdentityHttpClientTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/core/http/WorkloadIdentityHttpClientTest.kt @@ -10,10 +10,12 @@ import java.util.concurrent.ExecutionException import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.any import org.mockito.kotlin.argThat +import org.mockito.kotlin.doThrow import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -134,6 +136,79 @@ internal class WorkloadIdentityHttpClientTest { ) } + @Test + fun close_closesAuthAndDelegate() { + val workloadIdentityAuth = mock() + val delegateHttpClient = mock() + val client = WorkloadIdentityHttpClient(delegateHttpClient, workloadIdentityAuth) + + client.close() + + verify(workloadIdentityAuth).close() + verify(delegateHttpClient).close() + } + + @Test + fun close_whenAuthCloseFails_stillClosesDelegate() { + val authFailure = IllegalStateException("auth close failed") + val workloadIdentityAuth = mock() + val delegateHttpClient = mock() + doThrow(authFailure).whenever(workloadIdentityAuth).close() + val client = WorkloadIdentityHttpClient(delegateHttpClient, workloadIdentityAuth) + + val thrown = assertThrows { client.close() } + + assertThat(thrown).isSameAs(authFailure) + verify(workloadIdentityAuth).close() + verify(delegateHttpClient).close() + } + + @Test + fun close_whenAuthAndDelegateCloseFail_suppressesDelegateFailure() { + val authFailure = IllegalStateException("auth close failed") + val delegateFailure = IllegalArgumentException("delegate close failed") + val workloadIdentityAuth = mock() + val delegateHttpClient = mock() + doThrow(authFailure).whenever(workloadIdentityAuth).close() + doThrow(delegateFailure).whenever(delegateHttpClient).close() + val client = WorkloadIdentityHttpClient(delegateHttpClient, workloadIdentityAuth) + + val thrown = assertThrows { client.close() } + + assertThat(thrown).isSameAs(authFailure) + assertThat(thrown.suppressed).containsExactly(delegateFailure) + verify(workloadIdentityAuth).close() + verify(delegateHttpClient).close() + } + + @Test + fun close_whenDelegateCloseFails_propagatesFailure() { + val failure = IllegalStateException("delegate close failed") + val workloadIdentityAuth = mock() + val delegateHttpClient = mock() + doThrow(failure).whenever(delegateHttpClient).close() + val client = WorkloadIdentityHttpClient(delegateHttpClient, workloadIdentityAuth) + + assertThat(assertThrows { client.close() }).isSameAs(failure) + verify(workloadIdentityAuth).close() + verify(delegateHttpClient).close() + } + + @Test + fun close_whenBothThrowSameFailure_doesNotSuppressItself() { + val failure = IllegalStateException("shared close failure") + val workloadIdentityAuth = mock() + val delegateHttpClient = mock() + doThrow(failure).whenever(workloadIdentityAuth).close() + doThrow(failure).whenever(delegateHttpClient).close() + val client = WorkloadIdentityHttpClient(delegateHttpClient, workloadIdentityAuth) + + assertThat(assertThrows { client.close() }).isSameAs(failure) + assertThat(failure.suppressed).isEmpty() + verify(workloadIdentityAuth).close() + verify(delegateHttpClient).close() + } + private fun mockResponse(statusCode: Int, body: String): HttpResponse { return object : HttpResponse { override fun statusCode() = statusCode From 73a523eecf12b3654033b95bfd5c2011eb94643f Mon Sep 17 00:00:00 2001 From: Daniel Piet Date: Mon, 5 Oct 2026 19:07:44 +0000 Subject: [PATCH 2/2] fix: preserve public generation baseline for handwritten cleanup --- .castiron.stats.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.castiron.stats.yml b/.castiron.stats.yml index 2b748ad83..f2ac0d2ec 100644 --- a/.castiron.stats.yml +++ b/.castiron.stats.yml @@ -1,8 +1,8 @@ schema_version: 1 -generation_id: 7b64f44d-477d-4498-831f-1eeae1ce8a60 -openapi_spec_hash: 982e032cd9bc8921825601fbb36a8a92 -openapi_transformed_spec_hash: 7554bcd8a14262861882411fc4426386 -config_hash: ac46b7ace0a09008785717fbff2f1d19 -codegen_sha: 025c61e33941e37fa48d15f37af364f31e03fece -codegen_hash: 88003cad1add3c75fc7037929aa52ec1d4113b49586efe282ac37e8aa22f37ea -public_codegen_sha: ec0f52cdb65b936ec1850ee6041dae200fe5b126 +generation_id: 4a45859f-2ddc-4ae8-8d48-7d66ac4e16fe +openapi_spec_hash: 8aa23d19137079c724365bd6cadd0858 +openapi_transformed_spec_hash: d591fedadfdd68837534b2574782bf81 +config_hash: 70e6e763ed8ac8c4038de193a96c771d +codegen_sha: 460e3ec26544481b476774331018f126c57454d6 +codegen_hash: c35eb5de9f3bd898d3bf8745619d12d9ffcc5bc4e3ca332d978e6b42813243fd +public_codegen_sha: 0f5e1cc6093a03afec0cc7ad21d18921436e173f