Commit Graph

7 Commits

Author SHA1 Message Date
didericis-claude a3a9ec065e feat(cleanup): walk every backend, reap smolmachines orphans too
test / unit (pull_request) Successful in 26s
test / integration (pull_request) Successful in 43s
test / unit (push) Successful in 29s
test / integration (push) Successful in 38s
`./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>
2026-05-27 19:56:41 -04:00
didericis aee249f119 refactor(cleanup): compose-ls driven, plus orphan state-dir reaping
test / unit (pull_request) Successful in 17s
test / integration (pull_request) Successful in 1m9s
PRD 0018 chunk 4. `claude-bottle cleanup` now derives its work
from `docker compose ls --all --format json`, filtered to projects
whose name starts with `claude-bottle-`. Per project: one `compose
down --volumes` removes the containers + the compose-managed
networks atomically.

The plan also enumerates three fallback buckets:

  - Stray containers — `claude-bottle-*` containers with no
    `com.docker.compose.project` label (left over from pre-compose
    code paths). Cleared via `docker rm -f`.
  - Stray networks — `claude-bottle-*` networks with no compose
    project label. Cleared via `docker network rm`.
  - Orphan state dirs — per-bottle `~/.claude-bottle/state/<id>/`
    dirs with no live project AND no `.preserve` marker. The
    `.preserve` marker (capability-block or auto-preserve-on-crash)
    explicitly opts-out of reaping; manual `rm -rf` is the only
    path for preserved state.

cli/cleanup.py collapses to a single y/N prompt — backend.prepare_cleanup
returns everything in one plan, backend.cleanup processes everything,
no more double-prompt for state. The CLI-side state-dir enumeration
+ `_state_summary` flags from PR #25 are gone; the backend's
orphan-detection rules subsume them.
2026-05-25 23:48:02 -04:00
didericis fb2b5844c4 feat(cleanup): prompt to remove per-bottle state, separately from containers
test / unit (pull_request) Successful in 17s
test / integration (pull_request) Successful in 1m34s
`cli.py cleanup` already enumerated orphan containers + networks
and asked for confirmation before nuking them. Per-bottle state
under ~/.claude-bottle/state/ wasn't touched — accumulated forever,
including orphans from old code paths.

Add state to the cleanup flow with its own prompt: the trade-off is
different from containers (which are pure debris) because a state
dir may carry a resumable bottle (capability-block rebuild +
transcript snapshot) the operator still wants.

Output shows the resumable / orphan / rebuilt-Dockerfile / transcript /
preserve-marker flags for each state dir so the operator sees what
they'd lose. Both sections are skippable independently — answering
"n" to containers doesn't skip the state prompt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 06:56:04 -04:00
didericis 70a22fa210 refactor: rename platform abstraction to backend
test / run tests/run_tests.py (pull_request) Successful in 21s
Across the package:
  - claude_bottle/platform/         -> claude_bottle/backend/
  - platform/docker/platform.py     -> backend/docker/backend.py
  - class BottlePlatform            -> BottleBackend
  - class DockerBottlePlatform      -> DockerBottleBackend
  - get_bottle_platform()           -> get_bottle_backend()
  - env var CLAUDE_BOTTLE_PLATFORM  -> CLAUDE_BOTTLE_BACKEND
  - dict _PLATFORMS                 -> _BACKENDS

"Backend" is shorter and more established as the term for a
pluggable strategy-pattern implementation. "Platform" was vague
(could mean OS, hardware, cloud) and mildly redundant — Docker is
itself a platform.

The previous PRD section claiming "the Backend protocol was
rejected" referred to a low-level run/exec/cp/network_connect
protocol; the name was never the reason. The PRD is updated to
describe that rejected design by shape rather than by name.

The bottle/agent concepts and the manifest schema are unchanged.
2026-05-10 23:59:38 -04:00
didericis 1d2c18eaae refactor(platform): rename claude_bottle/bottles -> claude_bottle/platform
test / run tests/run_tests.py (pull_request) Successful in 13s
'bottles' was the package name when it held a single Bottle Protocol;
since we added BottlePlatform / BottlePlan / BottleCleanupPlan and
made it the home of platform dispatch, 'platform' describes the
package better. The 'bottle' concept (and the manifest field) stays.

CLI imports update from ..bottles to ..platform; internal relative
imports inside the package survive the rename unchanged. Git
detected all 7 file renames.
2026-05-10 23:37:28 -04:00
didericis 18d29fc23f refactor(bottles): two-phase cleanup parallel to prepare/launch
test / run tests/run_tests.py (pull_request) Successful in 13s
cmd_cleanup used to only sweep running containers via `docker ps`,
missing stopped pipelock sidecars and orphaned networks entirely. On
my host the new version surfaced ~10 stranded networks left behind by
SIGKILLed sessions — the kind of thing the old command implied it was
handling.

New shape, symmetric with start:
- BottleCleanupPlan (abstract, in bottles/__init__.py) with `print` +
  `empty` abstract members.
- DockerBottleCleanupPlan (concrete, in bottles/docker.py) carrying
  the resolved tuples of containers and networks.
- BottlePlatform gains abstract prepare_cleanup() + cleanup(plan).
  DockerBottlePlatform implements both:
    - prepare_cleanup: docker ps -a + docker network ls, both
      filtered to ^claude-bottle-, sorted for stable output.
    - cleanup: docker rm -f containers first (they hold the network
      attachment), then docker network rm.
- cmd_cleanup is now ~25 lines: prepare → print → y/N → cleanup.
2026-05-10 23:14:54 -04:00
didericis f817847dff refactor(cli): split claude_bottle/cli.py into a package
test / run tests/run_tests.py (push) Successful in 20s
One file per subcommand under claude_bottle/cli/, with shared constants
and the tty helper in _common.py and dispatch in __init__.py. The
public import (from claude_bottle.cli import main) is unchanged, so
the root cli.py entrypoint and the test suite see no surface change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 00:15:16 -04:00