Skip to content

fix(runtime): close the docker client on session cleanup#787

Merged
0xallam merged 1 commit into
mainfrom
fix/sandbox-teardown-close-client
Jul 16, 2026
Merged

fix(runtime): close the docker client on session cleanup#787
0xallam merged 1 commit into
mainfrom
fix/sandbox-teardown-close-client

Conversation

@0xallam

@0xallam 0xallam commented Jul 16, 2026

Copy link
Copy Markdown
Member

Problem

session_manager.cleanup() removes the sandbox container via client.delete() but never closes the Docker SDK client. docker-py talks to the daemon over requests/urllib3, so the connection pool is left for the interpreter to finalize at GC/shutdown. urllib3's response finalizers then raise:

Exception ignored in: <urllib3.response.HTTPResponse object at 0x...>
  File ".../urllib3/response.py", line 1289, in close
    self._fp.close()
  ...
ValueError: I/O operation on closed file.

These go straight to stderr via sys.unraisablehook, bypassing the logger entirely (0 occurrences in strix.log), which is why they show up as unexplained terminal spam. A run that dies mid-exec orphans a batch of responses at once — an observed failure printed ~25 identical tracebacks back to back.

Fix

Close the client after delete(), best-effort so cleanup never blocks the next scan.

  • Per-scan safe: backends.py:50 builds a fresh docker.from_env() per session, so closing one scan's client cannot affect a concurrent scan.
  • getattr guard keeps this resilient if the SDK renames the attribute.
  • Failures are logged at debug and swallowed, consistent with the existing best-effort cleanup contract.

Scope / honesty

These finalizer errors are cosmetic — an exception raised in a finalizer is caught and printed by CPython; it cannot propagate or affect a run's outcome. This is noise reduction plus correct deterministic teardown, not a behavior fix.

I was not able to reproduce the finalizer noise in a standalone harness (tried clean execs and abandoned streaming execs with the container force-removed underneath), so there is no before/after proof that it eliminates every occurrence. The change is correct-by-construction — the tracebacks are in urllib3, which reaches Docker via requests/docker-py (LiteLLM/OpenAI use httpx, so they're excluded), and docker_client.close()api.close() tears down exactly that pool. If any responses originate from the SDK's exec transport rather than docker-py's pool, those would need a separate fix.

Follow-up (not in this PR)

The same teardown path has a more serious gap: when delete() raises (e.g. 409 Conflict on an already-stopped container), the container is left un-reaped. An orphaned sandbox kept writing to its unbounded log and reached 147 GB, filling the host disk and taking the daemon down. #785 caps the log; force-reaping on delete() failure is still open and worth its own change.

Verification

  • ruff-lint, ruff-format, mypy, bandit, pyupgrade all pass (full pre-commit suite).
  • Verified client.close() / client.api.close() exist in the pinned docker-py (7.1.0).

🤖 Generated with Claude Code

cleanup() removed the container but never closed the Docker SDK client, so
docker-py's requests/urllib3 connection pool was left for the interpreter to
finalize at GC/shutdown. urllib3 response finalizers then raise "I/O operation
on closed file" straight to stderr, bypassing the logger. A run that dies
mid-exec orphans a batch of responses at once and floods the terminal with
identical tracebacks.

Close the client after delete(), best-effort so cleanup never blocks the next
scan. The client is per-scan (backends.py builds a fresh docker.from_env() per
session), so this cannot affect a concurrent scan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds deterministic Docker client cleanup for completed sandbox sessions. The main changes are:

  • Reuses a local reference for sandbox deletion.
  • Closes the session-owned Docker SDK client after deletion.
  • Logs and suppresses close failures to preserve best-effort cleanup.

Confidence Score: 5/5

This looks safe to merge.

  • The Docker client is created per sandbox session.
  • Cleanup closes it only after deletion finishes or raises.
  • Close failures remain contained by the existing best-effort cleanup flow.
  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
strix/runtime/session_manager.py Adds best-effort Docker SDK client closure after sandbox deletion, including deletion-failure paths.

Reviews (1): Last reviewed commit: "fix(runtime): close the docker client on..." | Re-trigger Greptile

@0xallam
0xallam merged commit af65796 into main Jul 16, 2026
1 check passed
@0xallam
0xallam deleted the fix/sandbox-teardown-close-client branch July 16, 2026 18:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant