Skip to content

fix(rm): remove volumes a devcontainer named after the workspace - #687

Open
blooop wants to merge 2 commits into
mainfrom
fix/rm-recorded-mount-volumes
Open

blooop wants to merge 2 commits into
mainfrom
fix/rm-recorded-mount-volumes

Conversation

@blooop

@blooop blooop commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

dl <ws> rm now removes every Docker volume that a devcontainer named after the workspace, not only <basename>-pixi and dind-var-lib-docker-<id>.

Evidence

A real run on one host, docker 29, with this branch's dl (release build):

$ dl kinisi-robotics/kinisi-ansible@main -- true          # exit 0
$ docker volume ls --format '{{.Name}}' | grep sll1
kinisi-ansible-pixi-kinisi-ansible-main-sll1
$ jq '.MergedConfig.mounts' ~/.devpod/contexts/default/workspaces/kinisi-ansible-main-sll1/workspace_result.json
[{"type":"volume","source":"kinisi-ansible-pixi-kinisi-ansible-main-sll1","target":"/workspaces/kinisi_ansible/.pixi"}]
$ dl kinisi-robotics/kinisi-ansible@main rm
Removed workspace kinisi-ansible-main-sll1.               # exit 0
$ docker volume ls --format '{{.Name}}' | grep sll1
                                                          # no output: the volume is gone

Before: the same two commands with released dl 0.62.0 left kinisi-ansible-pixi-kinisi-ansible-main-sll1 on the host. That repository's devcontainer declares source=kinisi-ansible-pixi-${localWorkspaceFolderBasename},…,type=volume, and the name does not match either hard-coded template. So every dl workspace of that repository leaked one volume. Another workspace of the same repository (kinisi-ansible-devcontainer-prebuild-jghy) was on the host during the run and stayed.

What this changes

  • Substitutions also reads the source of each type=volume entry in MergedConfig.mounts from devpod's workspace_result.json. It reads both mount spellings: the object and the --mount-style string.
  • volume_names() adds a recorded source when it holds the substituted basename or devcontainer id as a whole token. A whole token starts and ends at an end of the name or at a non-alphanumeric character. So the basename kr matches kr-cache and data.kr, but not kros-cache.
  • A fixed-name volume (myrepo-pip-cache) is not removed. Every workspace of the repository mounts the same one, and that sharing is usually on purpose.
  • The names are still devpod's record, verbatim. Nothing comes from a pattern over docker volume ls, so devlaunch#451's rule holds. The kept copy that --prune reads uses the same parser, so --prune gets the same names.
  • docs/cleanup.md gets a third row in the volume table.

Where this fits

 volume_names(record)
   <basename>-pixi
   dind-var-lib-docker-<devcontainerId>
+  for each MergedConfig.mounts entry with type=volume
+    if its source holds <basename> or <devcontainerId> as a whole token
+      its source, as devpod recorded it
   (duplicates dropped, order kept)

How I checked it

New tests that failed before the fix and pass now:

  • a_recorded_volume_mount_scoped_to_the_workspace_is_named: a record with scoped volumes, fixed-name volumes and binds in both spellings → only the scoped volumes are named.
  • a_scope_inside_a_longer_word_names_nothing: basename kr, id abc → kros-cache, shared-kr2-cache and beefcake-abc1 are not named.
  • a_recorded_volume_mount_with_no_substitution_names_nothing.
  • a_removed_workspaces_devcontainer_volumes_go_with_it and the purge test in dl/tests/lifecycle.rs: the fixture record now has a scoped string-form volume and a shared one. The docker volume rm argv gains only the scoped one.

Commands, with the pinned 1.97.1 toolchain from the pixi env:

  • cargo test --workspace --no-fail-fast: 3132 passed, 5 failed. The same 5 fail on main at 9af13ca on this host, and the change does not touch them (*pushed_by_url*, a_remote_that_cannot_be_reached_*, a_host_that_prunes_on_fetch_*). They depend on the host's git config.
  • cargo clippy --locked --all-targets -- -D warnings, cargo fmt --check, prek run --from-ref origin/main: clean.
  • I did not run pixi run test-e2e. The real-devpod run above replaces it for this path.

Merge danger

Door: two-way. A revert gives back the two-template behavior. No file format changes, and the kept-copy JSON keeps its shape.

Blast radius: small. If the scope test is wrong, dl rm can remove a volume that another workspace's devcontainer also mounts. Docker refuses to remove a volume that a container uses, and dl rm reports that as a warning. A stopped workspace's volume does not have that protection. The whole-token rule limits the risk to a basename or id that is a full token of the other volume's name. dl workspace ids end in a random 4-character suffix, so this needs a non-dl workspace with a short name.

Review notes

  • dl rm still prints no line for each volume that it removes. A successful sweep is silent by design (VolumeSweep::Removed). A line for each volume would change the rm and --purge output and the public-API snapshot, so I left it for a separate PR.

🤖 Generated with Claude Code

Summary by Sourcery

Clean up all recorded devcontainer volumes associated with a workspace without removing intentionally shared volumes.

Bug Fixes:

  • Ensure workspace removal cleans up devcontainer-created Docker volumes whose recorded names are scoped to the workspace, beyond the previously supported volume naming patterns.

Enhancements:

  • Preserve shared fixed-name volumes while recognizing workspace-scoped volume mounts from both supported devcontainer mount formats and applying the same names during pruning.

Documentation:

  • Document cleanup of additional workspace-scoped devcontainer volume mounts and retention of shared volumes.

Tests:

  • Add coverage for scoped and shared volume detection, token-boundary matching, missing substitutions, workspace removal, and purge behavior.

dl rm swept only two hard-coded volume names (<basename>-pixi and
dind-var-lib-docker-<devcontainerId>). A devcontainer that names its
volume any other way, such as
kinisi-ansible-pixi-${localWorkspaceFolderBasename}, leaked one volume
per workspace.

Read the volume mounts from the merged config devpod records in
workspace_result.json. A recorded source that holds the workspace's
substituted basename or devcontainer id is scoped to that workspace and
is removed with it. A fixed-name volume is shared between workspaces
and is left alone. The names stay devpod's record, never a pattern over
docker volume ls.
A plain substring test let a short basename such as kr match a shared
volume such as kros-cache. Require the basename or devcontainer id to
be bounded by the ends of the name or by a non-alphanumeric character.
@sourcery-ai

sourcery-ai Bot commented Oct 17, 2026

Copy link
Copy Markdown

Sorry @blooop, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 2 days and 8 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Oct 17, 2026

Copy link
Copy Markdown

Reviewer's Guide

Updates workspace cleanup to derive additional Docker volumes from devpod's recorded MergedConfig.mounts, removing only names scoped to the workspace basename or devcontainer ID while preserving shared fixed-name volumes. Parsing, token-boundary filtering, kept-copy pruning, lifecycle behavior, tests, and documentation are updated consistently.

Sequence diagram for workspace-scoped volume cleanup

sequenceDiagram
    participant User
    participant DL as dl
    participant Record as workspace_result.json
    participant Sweep as VolumeSweep
    participant Docker

    User->>DL: rm
    DL->>Record: substitutions_in(document)
    Record-->>DL: basename, devcontainerId, MergedConfig.mounts
    DL->>DL: volume_names()
    DL->>DL: holds_token(source, scope)
    DL->>Sweep: sweep_volumes(named)
    Sweep->>Docker: docker volume rm scoped names
    Docker-->>Sweep: removal result
    Sweep-->>User: workspace removed
Loading

Flow diagram for selecting workspace-scoped volumes

flowchart TD
    A[Read MergedConfig.mounts] --> B{Mount is type=volume?}
    B -- No --> Z[Ignore mount]
    B -- Yes --> C[Read recorded source]
    C --> D{Source holds basename or devcontainerId as a whole token?}
    D -- Yes --> E[Include recorded source]
    D -- No --> F[Preserve shared fixed-name volume]
    E --> G[Deduplicate and keep order]
    F --> G
Loading

File-Level Changes

Change Details Files
Discover and remove additional workspace-scoped Docker volumes from devpod's recorded merged configuration.
  • Capture sources from object-form and --mount-style volume entries in MergedConfig.mounts.
  • Select recorded sources only when the workspace basename or devcontainer ID appears as a whole token; preserve fixed/shared volumes.
  • Combine discovered names with existing templates, deduplicate them, and reuse the same logic for kept copies and pruning.
rust/devlaunch-core/src/flows/kept_copies.rs
rust/devlaunch-core/src/flows/lifecycle/delete.rs
Expand automated coverage for volume discovery and deletion behavior.
  • Add unit tests for mount parsing, token-boundary matching, missing substitutions, shared-volume exclusion, and both mount spellings.
  • Update lifecycle and purge fixtures to verify scoped volumes are removed while shared volumes remain.
rust/devlaunch-core/src/flows/kept_copies.rs
rust/dl/tests/lifecycle.rs
rust/dl/tests/lifecycle_scenario.py
Document that cleanup removes all recorded workspace-scoped devcontainer volumes while retaining intentionally shared volumes.
  • Add the generalized volume-selection rule and shared-volume exception to the cleanup volume table.
docs/cleanup.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codecov

codecov Bot commented Oct 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.24561% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.60%. Comparing base (9af13ca) to head (3d9c459).

Files with missing lines Patch % Lines
rust/devlaunch-core/src/flows/kept_copies.rs 98.24% 2 Missing ⚠️
Additional details and impacted files
Flag Coverage Δ
python 42.98% <ø> (ø)
rust 94.81% <98.24%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
shipped code (rust) 94.81% <98.24%> (+<0.01%) ⬆️
harness and tooling (python) 42.98% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

This branch has not been deployed

No deployments
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