User Story
As a Python application developer managing sandboxes through with Sandbox(...), I want SDK-owned client resources to be released when entering the context fails, so that failed initialization does not leave application-owned connections waiting for manual cleanup or garbage collection.
Problem Statement
After SandboxClient.from_active_cluster() returns a client, Sandbox.__enter__() stores it and then creates or retrieves a session and waits for readiness. If one of those operations raises, the exception propagates without calling client.close() or clearing the stored client reference.
Python does not invoke __exit__() when __enter__() raises. The existing cleanup in Sandbox.__exit__() therefore does not handle these failures.
This was reproduced against commit cfcc3733bd9177f29b3c2aceec9c052b8814422b using the unmodified SDK with injected failures. The Sandbox constructor, entry, and exit source is identical in stable release v0.1.2; that release was compared statically, not separately executed. A fresh check of main at 252882f37f2daa63ef4078b3bbb89adbcf43b409 also found the affected file unchanged (blob 20d473444345cc003348f886f7375f70d9aa62a5).
Impact / Why This Matters
Applications that retry failed initialization cannot rely on the context manager to promptly close its client resources. SandboxClient.close() owns both gRPC channel closure and the bearer-auth cleanup callback; neither cleanup path is invoked by the context manager in the reproduced failure cases.
A caller can retain the Sandbox object and manually close its private client, or use the lower-level client API with explicit finally cleanup, but both workarounds defeat the high-level context manager's lifecycle abstraction.
The reproduction establishes missing cleanup calls. It does not measure production socket growth or demonstrate a remote sandbox leak.
Acceptance Criteria
This report concerns local SDK client ownership. Any change to deletion of new or existing remote sandboxes should be explicitly agreed as a separate behavior decision.
Reproduction Steps
- Check out commit
cfcc3733bd9177f29b3c2aceec9c052b8814422b.
- Prepare the SDK and generated bindings:
uv run --frozen --python 3.11 python -X utf8 tasks/scripts/generate_python_proto.py.
- Save this script as
reproduce_entry_cleanup.py and run it with uv run --frozen --python 3.11 python -X utf8 reproduce_entry_cleanup.py:
from unittest.mock import Mock, patch
from openshell import Sandbox, SandboxClient, SandboxError
auth_close = Mock()
client = SandboxClient("127.0.0.1:1", _bearer_close=auth_close)
original = SandboxError("injected session creation failure")
managed = Sandbox(workspace="default", delete_on_exit=False)
try:
with (
patch.object(SandboxClient, "from_active_cluster", return_value=client),
patch.object(client, "create_session", side_effect=original),
patch.object(client, "close", wraps=client.close) as close,
):
try:
with managed:
raise AssertionError("the body should not be entered")
except SandboxError as caught:
assert caught is original
print("client.close calls:", close.call_count)
print("auth cleanup calls:", auth_close.call_count)
print("client reference retained:", managed._client is client)
finally:
# Close the diagnostic's real gRPC channel after observing the SDK behavior.
client.close()
The gateway operations are mocked; no running gateway or network request is required. The real SDK context manager and client close implementation are exercised.
Environment
- OpenShell: source checkout at
cfcc3733bd9177f29b3c2aceec9c052b8814422b.
- OS: Windows, PowerShell.
- Python: 3.11.6.
- Locked dependencies: grpcio 1.78.0, httpx 0.28.1, protobuf 6.33.5.
- Integration: unit-level failure injection; no Docker, GPU, or live gateway.
Logs
client.close calls: 0
auth cleanup calls: 0
client reference retained: True
The extended diagnostic exercised five failure paths (ordinary creation, template creation, existing-session retrieval, readiness for a new sandbox, and readiness for an existing sandbox). All had zero close calls. A successful context with delete_on_exit=False called client and auth cleanup once each and cleared the client reference.
Contributor Context
Contributor vouch request: #3921 (pending maintainer review).
User Story
As a Python application developer managing sandboxes through
with Sandbox(...), I want SDK-owned client resources to be released when entering the context fails, so that failed initialization does not leave application-owned connections waiting for manual cleanup or garbage collection.Problem Statement
After
SandboxClient.from_active_cluster()returns a client,Sandbox.__enter__()stores it and then creates or retrieves a session and waits for readiness. If one of those operations raises, the exception propagates without callingclient.close()or clearing the stored client reference.Python does not invoke
__exit__()when__enter__()raises. The existing cleanup inSandbox.__exit__()therefore does not handle these failures.This was reproduced against commit
cfcc3733bd9177f29b3c2aceec9c052b8814422busing the unmodified SDK with injected failures. TheSandboxconstructor, entry, and exit source is identical in stable release v0.1.2; that release was compared statically, not separately executed. A fresh check ofmainat252882f37f2daa63ef4078b3bbb89adbcf43b409also found the affected file unchanged (blob20d473444345cc003348f886f7375f70d9aa62a5).Impact / Why This Matters
Applications that retry failed initialization cannot rely on the context manager to promptly close its client resources.
SandboxClient.close()owns both gRPC channel closure and the bearer-auth cleanup callback; neither cleanup path is invoked by the context manager in the reproduced failure cases.A caller can retain the
Sandboxobject and manually close its private client, or use the lower-level client API with explicitfinallycleanup, but both workarounds defeat the high-level context manager's lifecycle abstraction.The reproduction establishes missing cleanup calls. It does not measure production socket growth or demonstrate a remote sandbox leak.
Acceptance Criteria
delete_on_exit=Falsesemantics.This report concerns local SDK client ownership. Any change to deletion of new or existing remote sandboxes should be explicitly agreed as a separate behavior decision.
Reproduction Steps
cfcc3733bd9177f29b3c2aceec9c052b8814422b.uv run --frozen --python 3.11 python -X utf8 tasks/scripts/generate_python_proto.py.reproduce_entry_cleanup.pyand run it withuv run --frozen --python 3.11 python -X utf8 reproduce_entry_cleanup.py:The gateway operations are mocked; no running gateway or network request is required. The real SDK context manager and client close implementation are exercised.
Environment
cfcc3733bd9177f29b3c2aceec9c052b8814422b.Logs
The extended diagnostic exercised five failure paths (ordinary creation, template creation, existing-session retrieval, readiness for a new sandbox, and readiness for an existing sandbox). All had zero close calls. A successful context with
delete_on_exit=Falsecalled client and auth cleanup once each and cleared the client reference.Contributor Context
Contributor vouch request: #3921 (pending maintainer review).