Skip to content

feat(sandbox,cmd): auto-cleanup orphan sandbox containers + SIGTERM handler#26

Merged
RalianENG merged 1 commit into
mainfrom
feat/sandbox-auto-cleanup
May 13, 2026
Merged

feat(sandbox,cmd): auto-cleanup orphan sandbox containers + SIGTERM handler#26
RalianENG merged 1 commit into
mainfrom
feat/sandbox-auto-cleanup

Conversation

@RalianENG
Copy link
Copy Markdown
Owner

Summary

Crashed/killed kojuto sessions used to leave their sandbox containers behind, accumulating dozens per dev session and eventually clogging docker ps with stopped containers that the developer had to docker rm -f manually.

Two-part fix: label every kojuto-created container, sweep non-running orphans at scan startup. SIGTERM is now also caught so the existing Cleanup defer fires under more shutdown paths.

Changes

  • New SandboxContainerLabel = "kojuto.scan" constant. containerArgs adds --label=kojuto.scan=true to every container.
  • New sandbox.CleanupStaleSandboxContainers(ctx) lists labeled containers in exited/created/dead states (three separate docker ps calls — Docker filters AND, not OR) and batch-removes them with docker rm -f.
  • cmd/root.go runScan calls the sweep at startup; logs "reaped N orphan(s)" when count > 0; silently continues on docker errors.
  • scanSinglePackage signal handler extended to catch SIGTERM alongside SIGINT.
  • Filter set explicitly excludes running/paused/restarting so concurrent kojuto invocations stay untouched.

Test Plan

  • make test — all green
  • TestContainerArgs — label present in docker flag set
  • TestCleanupStaleSandboxContainers_Empty — no-op path (3 docker ps, 0 docker rm)
  • TestCleanupStaleSandboxContainers_FiltersByLabel — exact filter combination + forbidden-status enforcement
  • TestCleanupStaleSandboxContainers_RemovesIDs — batched docker rm -f <ids...> contract
  • Smoke: kojuto scan lodash -e npm exits CLEAN, docker ps -a --filter label=kojuto.scan returns 0 rows afterward

Related Issues

Known limitation: SIGKILL'd kojuto instances leave their container in running state and are NOT swept by this pass — running containers are deliberately protected so concurrent kojuto runs stay safe. Manual recovery: docker ps --filter label=kojuto.scan + manual docker rm -f.

…andler

Crashed/killed kojuto sessions used to leave their sandbox containers
behind, accumulating dozens per dev session and eventually clogging
docker ps with stopped (and sometimes running) kojuto-sandbox
containers that the developer had to `docker rm -f` manually.

Two-part fix:

1. Every container kojuto creates now carries the label
   `kojuto.scan=true` (new SandboxContainerLabel constant + a single
   `--label=` line in containerArgs).

2. At each scan startup, cmd/root.go's runScan calls
   sandbox.CleanupStaleSandboxContainers, which:
     - Lists labeled containers in `exited`, `created`, and `dead`
       states (three separate `docker ps` calls because Docker
       filters compose with AND, not OR).
     - Issues a single batched `docker rm -f <ids...>` for the
       collected IDs.
     - Returns (count, err); the caller prints a one-line
       "reaped N orphan(s)" notice when count > 0 and silently
       continues on Docker errors (the scan has its own clearer
       error path if Docker is genuinely unavailable).

The status filter explicitly excludes `running`, `paused`, and
`restarting` so concurrent kojuto invocations on the same host
keep their live containers untouched.

Signal handling extended:

  scanSinglePackage's signal handler now traps SIGTERM in addition
  to SIGINT, so parent-process kills, container stops, and init
  shutdowns all fire the Cleanup defer instead of leaving a stale
  container behind.

Known limitation: SIGKILL'd kojuto instances leave their container
in `running` state and are NOT swept by this pass — running
containers are protected to keep concurrent kojuto runs safe.
Manual recovery: `docker ps --filter label=kojuto.scan` + manual
`docker rm -f`. Every other failure mode (clean exit, SIGINT,
SIGTERM, panic with defer, normal timeout) is covered.

Tests:

  - TestContainerArgs verifies the label appears in the docker
    flag set.
  - TestCleanupStaleSandboxContainers_Empty pins the no-op path
    (three `docker ps` calls, no `docker rm`).
  - TestCleanupStaleSandboxContainers_FiltersByLabel pins the
    exact (label, status) filter set and rejects the forbidden
    statuses (running/paused/restarting) that would disturb live
    scans.
  - TestCleanupStaleSandboxContainers_RemovesIDs pins the
    `docker rm -f <ids...>` batched contract.

Smoke (Windows host, Docker Desktop):

  Before: docker ps showed 5 lingering containers from earlier
  dev session.

  After:
    1. `kojuto.exe scan lodash -e npm` runs and exits CLEAN.
    2. `docker ps -a --filter label=kojuto.scan` returns 0
       rows — the Cleanup defer reclaimed the container.
    3. Re-running with no cleanup gap remaining triggers the
       startup sweep to print "reaped N orphan sandbox
       container(s) from previous runs" if any do exist.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented May 13, 2026

Codecov Report

❌ Patch coverage is 71.87500% with 9 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
cmd/root.go 37.50% 3 Missing and 2 partials ⚠️
internal/sandbox/sandbox.go 83.33% 2 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@RalianENG RalianENG merged commit bdb6881 into main May 13, 2026
11 of 12 checks passed
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