feat(cleanup): walk every backend, reap smolmachines orphans too
`./cli.py cleanup` previously called only the env-var-selected backend's `prepare_cleanup` / `cleanup` — so a leftover smolvm machine + bundle container + bundle network from a crashed smolmachines bottle would survive a default `docker`-mode cleanup indefinitely. Smolmachines now has a real `cleanup` module (alongside `enumerate.py` from issue #77) that walks: - smolvm machines named `claude-bottle-*` (via `smolvm machine ls --json`) - bundle containers `claude-bottle-sidecars-*` - bundle networks `claude-bottle-bundle-*` Cleanup runs stop+delete on the machines, force-rm on the containers, network rm on the networks. Each step is best-effort so a failed rm doesn't block the rest. `cli.py cleanup` walks every backend in `known_backend_names()` and runs each backend's `cleanup` after a single y/N prompt that shows a combined plan. State dirs (`~/.claude-bottle/state/<slug>/`) are shared layout with the docker backend, which still owns the orphan-state-dir bucket. It now consults `enumerate_active_bottles()` for the cross-backend live identity set so a running smolmachines bottle's state dir isn't reaped during a cleanup. Tests: smolmachines cleanup (prepare + cleanup ordering + failure handling); cross-backend orphan protection on the docker state-dir check; CLI cmd_cleanup walks both backends, short- circuits on all-empty, aborts on N. 617 unit tests pass. End-to-end verified on this host: $ smolvm machine ls --json | jq '.[].name' "claude-bottle-researcher-m3hxd" $ ./cli.py cleanup --- smolmachines backend --- smolvm machine: claude-bottle-researcher-m3hxd remove all of the above? [y/N] Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit was merged in pull request #79.
This commit is contained in:
@@ -83,11 +83,18 @@ def _list_prefixed_networks() -> list[str]:
|
||||
return sorted(set(out))
|
||||
|
||||
|
||||
def _list_orphan_state_dirs(live_projects: set[str]) -> list[str]:
|
||||
def _list_orphan_state_dirs(
|
||||
live_projects: set[str], protected_identities: set[str],
|
||||
) -> list[str]:
|
||||
"""State identities whose compose project isn't running and
|
||||
that don't have a `.preserve` marker. `.preserve` means the
|
||||
user (or an auto-preserve-on-crash) wants the state kept for
|
||||
`resume`."""
|
||||
`resume`.
|
||||
|
||||
`protected_identities` is the set of slugs that are live in
|
||||
ANY backend — used so this docker-side check doesn't reap a
|
||||
running smolmachines bottle's state dir (the layout is shared
|
||||
across both backends)."""
|
||||
state_root = _supervise.claude_bottle_root() / "state"
|
||||
if not state_root.is_dir():
|
||||
return []
|
||||
@@ -99,6 +106,8 @@ def _list_orphan_state_dirs(live_projects: set[str]) -> list[str]:
|
||||
project = f"{COMPOSE_PROJECT_PREFIX}{identity}"
|
||||
if project in live_projects:
|
||||
continue
|
||||
if identity in protected_identities:
|
||||
continue
|
||||
if is_preserved(identity):
|
||||
continue
|
||||
orphans.append(identity)
|
||||
@@ -106,15 +115,25 @@ def _list_orphan_state_dirs(live_projects: set[str]) -> list[str]:
|
||||
|
||||
|
||||
def prepare_cleanup() -> DockerBottleCleanupPlan:
|
||||
"""Enumerate everything cleanup will touch. No removals."""
|
||||
"""Enumerate everything cleanup will touch. No removals.
|
||||
|
||||
Pulls the union of live identities across backends via
|
||||
`enumerate_active_agents()` so the orphan-state-dir bucket
|
||||
doesn't include slugs whose smolmachines VM is still up."""
|
||||
docker_mod.require_docker()
|
||||
projects = list_compose_projects()
|
||||
project_set = set(projects)
|
||||
# Late import to avoid a circular at module-load time —
|
||||
# the backend package's __init__ imports this module.
|
||||
from .. import enumerate_active_agents
|
||||
protected = {a.slug for a in enumerate_active_agents()}
|
||||
return DockerBottleCleanupPlan(
|
||||
projects=tuple(projects),
|
||||
stray_containers=tuple(_list_prefixed_containers()),
|
||||
stray_networks=tuple(_list_prefixed_networks()),
|
||||
orphan_state_dirs=tuple(_list_orphan_state_dirs(project_set)),
|
||||
orphan_state_dirs=tuple(
|
||||
_list_orphan_state_dirs(project_set, protected),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user