Problem
When a sandbox fails to start - due to node resource exhaustion, quota limits, image pull failures, scheduling contention, or other infrastructure issues - the SDK raises generic exceptions with no detail about the cause:
SandboxTerminatedError("Sandbox {id} was terminated") for sandboxes that reach TERMINATED
SandboxFailedError("Sandbox {id} failed to start") for sandboxes that reach FAILED
Both are misleading when the sandbox never ran. Users cannot tell whether their code executed at all, and the error message gives no hint about the underlying cause.
This affects any operation that waits for the sandbox to be ready: sandbox.wait(), await sandbox, and auto-waiting operations like exec(), read_file(), and write_file() on a sandbox that hasn't started yet.
Example
with Sandbox.run(container_image="my-image:latest") as sb:
result = sb.exec(["echo", "hello"]).result()
If the sandbox can't be scheduled (e.g. node has 56 MiB free, sandbox needs 512 MiB), the user sees:
cwsandbox.exceptions.SandboxTerminatedError: Sandbox bfd8ef2e-... was terminated
The actual cause required operator investigation of K8s events to diagnose. With better error surfacing, the message could indicate the scheduling or resource failure.
Backend dependency
The backend is adding structured CWSANDBOX_* error codes to synchronous RPCs (aviato#451), but the async failure path - where the SDK discovers a FAILED/TERMINATED status by polling Get - currently has no way to carry a failure reason. GetSandboxResponse has no reason field. This is tracked in aviato#633.
SDK work in this issue can proceed in parallel on what's possible today, then pick up the async path when the backend is ready.
What needs to happen in the SDK
-
Distinguish "failed to start" from "terminated while running": A sandbox that never reached RUNNING should not raise the same exception as one that was externally stopped mid-execution. The SDK's internal state machine (_Starting -> _Running -> _Terminal) can likely be used to infer this, though there is no explicit "ever reached RUNNING" flag today.
-
Extract structured error details from gRPC status: When the backend returns a grpc.RpcError with ErrorInfo/RetryInfo details containing a CWSANDBOX_* reason, the SDK should parse them and make them available - either in the exception message, as exception attributes (e.g. reason_code, reason_message, retry_delay), or both.
-
Surface status_reason from Get responses (blocked on aviato#633): When the backend adds a reason field to GetSandboxResponse, include it in the exception message and expose it as an attribute on SandboxFailedError/SandboxTerminatedError.
-
Consider retryability: Some backend errors include RetryInfo with suggested retry delays. The SDK could expose this to help users implement retry logic, or handle retries internally for transient errors.
Related
- aviato#633 - Backend: add
status_reason to GetSandboxResponse
Problem
When a sandbox fails to start - due to node resource exhaustion, quota limits, image pull failures, scheduling contention, or other infrastructure issues - the SDK raises generic exceptions with no detail about the cause:
SandboxTerminatedError("Sandbox {id} was terminated")for sandboxes that reach TERMINATEDSandboxFailedError("Sandbox {id} failed to start")for sandboxes that reach FAILEDBoth are misleading when the sandbox never ran. Users cannot tell whether their code executed at all, and the error message gives no hint about the underlying cause.
This affects any operation that waits for the sandbox to be ready:
sandbox.wait(),await sandbox, and auto-waiting operations likeexec(),read_file(), andwrite_file()on a sandbox that hasn't started yet.Example
If the sandbox can't be scheduled (e.g. node has 56 MiB free, sandbox needs 512 MiB), the user sees:
The actual cause required operator investigation of K8s events to diagnose. With better error surfacing, the message could indicate the scheduling or resource failure.
Backend dependency
The backend is adding structured
CWSANDBOX_*error codes to synchronous RPCs (aviato#451), but the async failure path - where the SDK discovers a FAILED/TERMINATED status by polling Get - currently has no way to carry a failure reason.GetSandboxResponsehas no reason field. This is tracked in aviato#633.SDK work in this issue can proceed in parallel on what's possible today, then pick up the async path when the backend is ready.
What needs to happen in the SDK
Distinguish "failed to start" from "terminated while running": A sandbox that never reached RUNNING should not raise the same exception as one that was externally stopped mid-execution. The SDK's internal state machine (
_Starting->_Running->_Terminal) can likely be used to infer this, though there is no explicit "ever reached RUNNING" flag today.Extract structured error details from gRPC status: When the backend returns a
grpc.RpcErrorwithErrorInfo/RetryInfodetails containing aCWSANDBOX_*reason, the SDK should parse them and make them available - either in the exception message, as exception attributes (e.g.reason_code,reason_message,retry_delay), or both.Surface
status_reasonfrom Get responses (blocked on aviato#633): When the backend adds a reason field toGetSandboxResponse, include it in the exception message and expose it as an attribute onSandboxFailedError/SandboxTerminatedError.Consider retryability: Some backend errors include
RetryInfowith suggested retry delays. The SDK could expose this to help users implement retry logic, or handle retries internally for transient errors.Related
status_reasontoGetSandboxResponse