From ea0b89205a644d68d7598677789e8253d54011f0 Mon Sep 17 00:00:00 2001 From: Mike Bannister Date: Tue, 17 Mar 2026 00:18:46 -0400 Subject: [PATCH] fix: close _stdout_stream in SubprocessCLITransport.close() close() properly closed _stdin_stream and _stderr_stream via .aclose() but only set _stdout_stream = None without closing it, leaking anyio TextReceiveStream objects and producing ResourceWarnings. --- .../_internal/transport/subprocess_cli.py | 5 +++++ tests/test_transport.py | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/claude_agent_sdk/_internal/transport/subprocess_cli.py b/src/claude_agent_sdk/_internal/transport/subprocess_cli.py index 7c142f69..40bc1cab 100644 --- a/src/claude_agent_sdk/_internal/transport/subprocess_cli.py +++ b/src/claude_agent_sdk/_internal/transport/subprocess_cli.py @@ -465,6 +465,11 @@ async def close(self) -> None: await self._stderr_stream.aclose() self._stderr_stream = None + if self._stdout_stream: + with suppress(Exception): + await self._stdout_stream.aclose() + self._stdout_stream = None + # Terminate and wait for process if self._process.returncode is None: with suppress(ProcessLookupError): diff --git a/tests/test_transport.py b/tests/test_transport.py index 7d09ecee..30413a02 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -234,8 +234,19 @@ async def _test(): assert transport._process is not None assert transport.is_ready() + # Replace internal streams with mocks to verify close() calls aclose() + mock_stdout_stream = MagicMock() + mock_stdout_stream.aclose = AsyncMock() + transport._stdout_stream = mock_stdout_stream + + mock_stderr_stream = MagicMock() + mock_stderr_stream.aclose = AsyncMock() + transport._stderr_stream = mock_stderr_stream + await transport.close() mock_process.terminate.assert_called_once() + mock_stdout_stream.aclose.assert_called_once() + mock_stderr_stream.aclose.assert_called_once() anyio.run(_test)