feat(sandbox,cmd): auto-cleanup orphan sandbox containers + SIGTERM handler#26
Merged
Conversation
…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 Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Crashed/killed kojuto sessions used to leave their sandbox containers behind, accumulating dozens per dev session and eventually clogging
docker pswith stopped containers that the developer had todocker rm -fmanually.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
SandboxContainerLabel = "kojuto.scan"constant.containerArgsadds--label=kojuto.scan=trueto every container.sandbox.CleanupStaleSandboxContainers(ctx)lists labeled containers inexited/created/deadstates (three separatedocker pscalls — Docker filters AND, not OR) and batch-removes them withdocker rm -f.cmd/root.gorunScancalls the sweep at startup; logs "reaped N orphan(s)" when count > 0; silently continues on docker errors.scanSinglePackagesignal handler extended to catchSIGTERMalongsideSIGINT.running/paused/restartingso concurrent kojuto invocations stay untouched.Test Plan
make test— all greenTestContainerArgs— label present in docker flag setTestCleanupStaleSandboxContainers_Empty— no-op path (3docker ps, 0docker rm)TestCleanupStaleSandboxContainers_FiltersByLabel— exact filter combination + forbidden-status enforcementTestCleanupStaleSandboxContainers_RemovesIDs— batcheddocker rm -f <ids...>contractkojuto scan lodash -e npmexits CLEAN,docker ps -a --filter label=kojuto.scanreturns 0 rows afterwardRelated Issues
Known limitation: SIGKILL'd kojuto instances leave their container in
runningstate 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+ manualdocker rm -f.