diff --git a/agentscope-extensions/agentscope-extensions-sandbox/agentscope-extensions-sandbox-kubernetes/src/main/java/io/agentscope/extensions/sandbox/kubernetes/Fabric8KubernetesPodRuntime.java b/agentscope-extensions/agentscope-extensions-sandbox/agentscope-extensions-sandbox-kubernetes/src/main/java/io/agentscope/extensions/sandbox/kubernetes/Fabric8KubernetesPodRuntime.java index 55eca25ab..367c6bb31 100644 --- a/agentscope-extensions/agentscope-extensions-sandbox/agentscope-extensions-sandbox-kubernetes/src/main/java/io/agentscope/extensions/sandbox/kubernetes/Fabric8KubernetesPodRuntime.java +++ b/agentscope-extensions/agentscope-extensions-sandbox/agentscope-extensions-sandbox-kubernetes/src/main/java/io/agentscope/extensions/sandbox/kubernetes/Fabric8KubernetesPodRuntime.java @@ -45,6 +45,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -173,7 +174,7 @@ private void hydrateWithArchive(KubernetesSandboxState state, InputStream archiv // Phase 1: Stream archive to a temp file via stdin. // "cat" exits immediately on EOF — no post-EOF work, no SIGKILL race. ByteArrayOutputStream errBuf = new ByteArrayOutputStream(); - Integer uploadCode; + CompletableFuture uploadExit; try (ExecWatch watch = pod(state) .redirectingInput() @@ -182,21 +183,22 @@ private void hydrateWithArchive(KubernetesSandboxState state, InputStream archiv try (OutputStream stdin = watch.getInput()) { archive.transferTo(stdin); } - try { - uploadCode = watch.exitCode().get(TAR_TIMEOUT_SECONDS + 10L, TimeUnit.SECONDS); - } catch (java.util.concurrent.TimeoutException e) { + uploadExit = watch.exitCode(); + } + try { + Integer code = uploadExit.get(TAR_TIMEOUT_SECONDS + 10L, TimeUnit.SECONDS); + if (code != null && code != 0) { throw new SandboxException.SandboxRuntimeException( SandboxErrorCode.WORKSPACE_ARCHIVE_READ_ERROR, - "archive upload to pod timed out"); + "archive upload failed (exit=" + + code + + "): " + + errBuf.toString(StandardCharsets.UTF_8)); } - } - if (uploadCode == null || uploadCode != 0) { + } catch (java.util.concurrent.TimeoutException e) { throw new SandboxException.SandboxRuntimeException( SandboxErrorCode.WORKSPACE_ARCHIVE_READ_ERROR, - "archive upload failed (exit=" - + uploadCode - + "): " - + errBuf.toString(StandardCharsets.UTF_8)); + "archive upload to pod timed out"); } // Phase 2: Extract from the temp file — no stdin involved, no race. diff --git a/agentscope-extensions/agentscope-extensions-sandbox/agentscope-extensions-sandbox-kubernetes/src/test/java/io/agentscope/extensions/sandbox/kubernetes/Fabric8KubernetesPodRuntimeTest.java b/agentscope-extensions/agentscope-extensions-sandbox/agentscope-extensions-sandbox-kubernetes/src/test/java/io/agentscope/extensions/sandbox/kubernetes/Fabric8KubernetesPodRuntimeTest.java index 123829f6d..397461efc 100644 --- a/agentscope-extensions/agentscope-extensions-sandbox/agentscope-extensions-sandbox-kubernetes/src/test/java/io/agentscope/extensions/sandbox/kubernetes/Fabric8KubernetesPodRuntimeTest.java +++ b/agentscope-extensions/agentscope-extensions-sandbox/agentscope-extensions-sandbox-kubernetes/src/test/java/io/agentscope/extensions/sandbox/kubernetes/Fabric8KubernetesPodRuntimeTest.java @@ -182,7 +182,7 @@ void tarWorkspaceIn_success_exitCodeZero() throws Exception { } @Test - void tarWorkspaceIn_uploadExitNull_throwsException() { + void tarWorkspaceIn_uploadExitNull_isTreatedAsSuccess() throws Exception { ContainerResource container = mock(ContainerResource.class); doReturn(container).when(podResource).inContainer(anyString()); @@ -203,11 +203,9 @@ void tarWorkspaceIn_uploadExitNull_throwsException() { doReturn(execWatch).when(writingErrExec).exec(anyString(), anyString(), anyString()); doReturn(CompletableFuture.completedFuture(0)).when(execWatch).exitCode(); - assertThrows( - SandboxException.SandboxRuntimeException.class, - () -> - runtime.tarWorkspaceIn( - createK8sState(), new ByteArrayInputStream("data".getBytes()))); + // Null exit code is expected when Websocket closes before stream 3 status arrives. + // The upload itself completed successfully — null should be treated as success. + runtime.tarWorkspaceIn(createK8sState(), new ByteArrayInputStream("data".getBytes())); } @Test