mirror of
https://github.com/usestrix/strix.git
synced 2026-08-21 02:45:31 +02:00
fix(runtime): swallow torn-down docker socket in sandbox delete() (#721)
StrixDockerSandboxClient.delete() best-effort-kills the sandbox container via containers.get(id).kill() before delegating to the SDK's delete(), suppressing docker NotFound/APIError. But when the docker daemon socket is already going away — the normal case on a host/CI teardown — containers.get() -> inspect_container raises requests' ConnectionError, which is a *sibling* of docker.errors.APIError under requests.RequestException, not a subclass. So it escapes the APIError-only suppress and surfaces a full traceback on teardown even though the kill is meant to be best-effort. Add RequestException to the suppress so the best-effort kill is genuinely best-effort regardless of daemon reachability. Test: tests/test_docker_client_delete.py — the kill raising ConnectionError (and NotFound/APIError) is swallowed and delete() still delegates; unrelated errors still propagate; no-container_id is a no-op. The ConnectionError case fails against the pre-fix APIError-only suppress. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e1940769de
commit
0fb005c73f
@@ -40,6 +40,7 @@ from docker import errors as docker_errors # type: ignore[import-untyped, unuse
|
||||
from docker.models.containers import Container # type: ignore[import-untyped, unused-ignore]
|
||||
from docker.types import Mount as DockerSDKMount # type: ignore[import-untyped, unused-ignore]
|
||||
from docker.utils import parse_repository_tag # type: ignore[import-untyped, unused-ignore]
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -148,6 +149,15 @@ class StrixDockerSandboxClient(DockerSandboxClient):
|
||||
async def delete(self, session: SandboxSession) -> SandboxSession:
|
||||
container_id = getattr(getattr(session._inner, "state", None), "container_id", None)
|
||||
if container_id:
|
||||
with contextlib.suppress(docker_errors.NotFound, docker_errors.APIError):
|
||||
# Best-effort kill: NotFound/APIError cover a gone or unhappy
|
||||
# container. RequestException covers a torn-down daemon socket —
|
||||
# containers.get() -> inspect_container raises requests'
|
||||
# ConnectionError, which is a sibling of docker.errors.APIError
|
||||
# under requests.RequestException (not a subclass), so it escapes
|
||||
# an APIError-only suppress and surfaces a full traceback even
|
||||
# though this teardown is meant to be best-effort.
|
||||
with contextlib.suppress(
|
||||
docker_errors.NotFound, docker_errors.APIError, RequestException
|
||||
):
|
||||
self.docker_client.containers.get(container_id).kill()
|
||||
return await super().delete(session)
|
||||
|
||||
Reference in New Issue
Block a user