anthropics / anthropics/claude-agent-sdk-python
ClaudeSDKClient can leak transports on double connect and pre-Query failures
- Langage dominant
- Python
- Étoiles
- 8.1k
- Forks
- 1.3k
- Métriques de merge des PR
- Métriques de PR en attente
Description
### Summary
Transport ownership is lost in two related connection lifecycle paths:
1. Calling `ClaudeSDKClient.connect()` twice overwrites the first live `Query` and transport without closing them.
2. An exception after `transport.connect()` but before a `Query` is assigned causes cleanup to discard the transport reference without closing it.
### Affected code
- `src/claude_agent_sdk/client.py:100-145`
- `src/claude_agent_sdk/client.py:187-229`
- `src/claude_agent_sdk/client.py:612-621`
- `src/claude_agent_sdk/_internal/client.py:127-174`
- `tests/test_streaming_client.py:1123-1141`
### Current behavior
`connect()` has no already-connected guard. A second call creates a new transport and assigns a new `Query`. The existing test only asserts that two transports were created; it does not assert that the first was closed.
For failures before `self._query = Query(...)`, the outer exception handler calls `disconnect()`. However, `disconnect()` only closes through `self._query`; when that is still `None`, it sets `self._transport = None` without calling `close()`.
One reachable example is an invalid `CLAUDE_CODE_STREAM_CLOSE_TIMEOUT`: integer conversion occurs after the subprocess connects but before Query construction.
The one-shot InternalClient has a similar ownership gap because `await chosen_transport.connect()` occurs before the `try/finally` that closes `query`.
### Why this matters
The abandoned transport can retain a CLI subprocess, stdin/stdout pipes, and background read tasks. Repeated failures or reconnect attempts can accumulate orphan processes and active sessions.
### Expected behavior
Every successfully or partially connected transport must have exactly one owner and must be closed on replacement or failure.
### Possible fix
- Reject a second `connect()` with `CLIConnectionError`, or explicitly disconnect the old connection before replacing it.
- Wrap the period between transport creation/connect and Query ownership transfer in a transport-level `try/finally`.
- Add assertions that the first transport is closed on double connect and that a connected transport is closed when setup fails before Query construction.
PR #1082 mentions the pre-Query transport gap as deliberately out of scope, but I could not find a dedicated issue or active fix for it.
### Environment
- Repository revision: current `main` audit at SDK version 0.2.128
- Bundled CLI version: 2.1.220
- Python test suite: 1,291 passed, 5 skipped
- Ruff and mypy: clean
I searched the existing issues and pull requests using the affected symbols and behavior before filing this.
Guide de contribution
Aucun guide de contribution indexé pour ce dépôt
Évaluation
Cette issue n'a pas encore été évaluée.