Skip to content

Keep devlaunch's own copy of the volume names, and reclaim from it - #516

Merged
blooop merged 3 commits into
mainfrom
wayfinder/devlaunch-456
Aug 29, 2026
Merged

Keep devlaunch's own copy of the volume names, and reclaim from it#516
blooop merged 3 commits into
mainfrom
wayfinder/devlaunch-456

Conversation

@blooop

@blooop blooop commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Builds #456, the decision #451 settled: keep devlaunch's own copy of the two
substituted volume names at the end of a completed up, and reclaim from that
copy the volumes of a workspace devpod has forgotten.

The existing 39 orphaned volumes are out of scope by decision and stay. No
route reaches them that is not the pattern #451 refuses.

The five seams, as built

1. The copy store is a new module, flows::kept_copies, with a per workspace
marker under <cache>/workspace-copies/<id>.json beside the tool verdict markers.
Three verbs, and the module doc argues each: keep (write on a completed up,
and it does the read itself, so there is one parse of one document), volumes
(read one, Option<NonEmpty<String>>, no empty-list state) with copied over
the directory for the enumeration, and forget (drop on proof). Modelled on
provision::verdict_cache down to the atomic write, and the one place it diverges
from that module's "nothing ever deletes a marker" carries its reason in the doc.

The parse moved here too: Substitutions, parse_substitutions and
volume_names used to live in flows::lifecycle. Now the live read at delete time
and the kept copy's read share one parser and agree by construction rather than by
two implementations being written alike.

The marker carries the image reference alongside the names, from the same read
at the same moment (ContainerDetails.Config.Image and MergedConfig.image kept
apart), per #450's breadcrumb, so #458 has one document and no second migration.
Nothing here removes an image.

2. The write site is the tail of a successful up, before the provisioning
pass, on both arms of launch::up_under_stage: after devpod up returns, and on
the arm where a sibling's up won the race (#452, since that sibling may not have
been dl). Before rather than after because provisioning can fail and take the
launch with it while the container and its volumes stand.

3. The plan is a second enumeration in prune_plan, over the set of copies
rather than the clone walk (#445's lesson: a copy whose clone was deleted by hand
names volumes no clone-shaped walk reaches). Names ride on each
ReclaimableVolumes record, never as a plan-wide list. Precondition per copy: no
workspace devpod list returns carries that id.

4. The act re-asks that precondition under the second devpod list the acting
pass already pays for, then goes through sweep_volumes, the same removal the
delete path uses. Removed drops the copy; Refused keeps it so the retry
survives; no docker is silent. dl <ws> rm drops the copy on the same proof, so a
delete that already swept does not leave the next prune reporting a reclaim.

5. The binary boundary: a_prune_reclaims_the_volumes_of_a_workspace_devpod_forgot_and_drops_the_copy
is the whole regression in one run, with docker's argv as the assertion.

Provenance is on the occasion, not the name

SweepOccasion is two arms, DevpodResult and KeptCopy, and there is no third,
so adding a pattern arm later is a compile error at every match. There is no
constructor for an inferred name to reach, which is what #451 asked for over a
Provenance field with a Pattern arm.

devlaunch-core/tests/volume_names.rs is the by-construction guard #451 asked for,
in tests/devpod_layout.rs's shape: the two name templates are spelled in one
module and nowhere else, and docker::remove_volumes is reachable from one call
site. Both halves are two-sided, so the guard cannot pass by the thing it guards
having vanished.

Tests

New: 15 in flows::kept_copies, 4 in flows::launch, 9 in flows::lifecycle,
3 in tests/volume_names.rs. Each was red before its seam existed.

cargo test --workspace          1436 + guards, all passing
cargo clippy --locked --all-targets -- -D warnings
cargo fmt --check
pytest test/test_docs_prose.py test/test_readme_cli_doc.py

Two things to know when reviewing

  • The promised surface moves by one declaration, deliberately.
    api::workspace_delete gains the copy store as its sixth parameter, because a
    delete that sweeps a workspace's volumes is also the moment that workspace's
    copy is provably pointless, and dropping it there is what keeps the next
    --prune from reporting a reclaim of volumes that already went. It is the only
    row in public-api.api.txt that changed, and its only caller in this repository
    is dl (Decide: wf links devlaunch-core vs keeps the wire seam — prototype before cutover #266: wf links none of this; dl --ls --json is its contract).

    Both snapshots were hand-edited, since this container has neither prerequisite
    scripts/public-api-snapshots.sh needs, and CI's first run caught the one thing
    no local check can: ordering is not a partition property, and SweepOccasion
    sorts after StopOutcome rather than before. That is taken from the job's own
    regenerated diff, verbatim.

  • A volume refusal does not change --prune's exit code. It is reported and
    the run is otherwise finished, which is the delete path's precedent: the
    workspace was already gone, and the copy is kept so the retry stays possible.

Docs: docs/cleanup.md gains the mechanism section and its "the --prune half of
the pair still frees no volume at all" paragraph is rewritten rather than deleted,
as the ticket asks. docs/development.md gains the scratch-cache sentence.
README's two prune rows and the CHANGELOG [Unreleased] follow.

Closes #456. Unblocks #458.

🤖 Generated with Claude Code

Summary by Sourcery

Preserve devpod’s recorded volume names and use them to safely reclaim volumes from forgotten workspaces during dl --prune.

New Features:

  • Keep per-workspace copies of devpod’s substituted volume names after completed launches so they remain available for cleanup if devpod later forgets the workspace.
  • Extend dl --prune to reclaim recorded volumes for workspaces no longer returned by devpod, while reporting reclaimed and retained volumes.
  • Carry image references in the kept workspace records for subsequent cleanup work.

Bug Fixes:

  • Prevent completed launches and workspace deletions from leaving devlaunch’s recorded volume names inconsistent with the volumes that remain.
  • Avoid reclaiming volumes when a workspace reappears between prune planning and execution, or when Docker refuses removal.

Enhancements:

  • Centralize substitution parsing and volume-name generation, and constrain volume naming and removal to verified code paths.
  • Drop kept copies only after successful volume removal, preserving them for retries after refusals.
  • Expose volume reclaim details and provenance in lifecycle reports and prune output.

Documentation:

  • Document kept volume copies, prune behavior, scratch-cache implications, and the intentionally out-of-scope legacy orphaned volumes.
  • Update README command and cleanup descriptions to reflect volume reclamation by dl --prune.

Tests:

  • Add coverage for kept-copy persistence, launch ordering, workspace deletion, prune planning and execution, race protection, refusal handling, and cache isolation.
  • Add source-level guards ensuring volume names have one construction path and Docker volume removal has one call site.

Chores:

  • Update the frozen public API snapshots for the expanded workspace deletion interface.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 6 days and 23 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR adds a cache-backed, provenance-preserving copy of devpod’s substituted volume names at completed-up boundaries, then extends prune to safely reclaim those volumes only after devpod forgets the workspace, with liveness revalidation, retryable refusal handling, delete integration, structural guards, tests, and documentation.

Sequence diagram for preserving volume names during up

sequenceDiagram
    participant User
    participant Launch as launch::up_under_stage
    participant Devpod
    participant KeptCopies as flows::kept_copies
    participant Provision

    User->>Launch: workspace_up()
    Launch->>Devpod: devpod up
    Devpod-->>Launch: completed up
    Launch->>KeptCopies: keep(workspace_id, devpod_home)
    KeptCopies->>Devpod: read workspace_result.json
    KeptCopies-->>Launch: copy written atomically
    Launch->>Provision: provision_tools()
    Provision-->>Launch: result
Loading

Sequence diagram for reclaiming forgotten workspace volumes

sequenceDiagram
    participant User
    participant Prune as dl --prune
    participant Devpod
    participant KeptCopies as flows::kept_copies
    participant Docker

    User->>Prune: prune_plan()
    Prune->>KeptCopies: copied()
    KeptCopies-->>Prune: workspace ids and volume names
    Prune->>Devpod: devpod list
    Devpod-->>Prune: live workspace ids
    Prune->>Prune: exclude listed workspaces
    User->>Prune: confirm and run
    Prune->>Devpod: devpod list
    Devpod-->>Prune: live workspace ids
    Prune->>Docker: volume rm --force names
    alt Removed
        Docker-->>Prune: success
        Prune->>KeptCopies: forget(workspace_id)
    else Refused
        Docker-->>Prune: volume is in use
        Prune-->>User: report refusal, keep copy for retry
    end
Loading

File-Level Changes

Change Details Files
Add a persistent per-workspace copy store for devpod’s substituted volume names and image metadata.
  • Parse substitution data once through the new shared module.
  • Write copies atomically after completed up operations, including sibling-race success paths.
  • Enumerate, read, and forget copies with unreadable or empty records treated as absent.
  • Keep copy data cache-scoped so scratch caches cannot discover or remove volumes.
rust/devlaunch-core/src/flows/kept_copies.rs
rust/devlaunch-core/src/flows/launch.rs
rust/devlaunch-core/src/flows/lifecycle.rs
rust/devlaunch-core/src/flows/mod.rs
Extend prune to reclaim volumes only for workspaces absent from the current devpod listing.
  • Build a separate reclaim plan from kept-copy records rather than clone directories.
  • Carry names on each reclaim record and re-check workspace liveness before acting.
  • Reuse the existing volume sweep path and remove copies only after successful removal.
  • Report refusals and preserve copies for retries without changing prune’s exit status.
rust/devlaunch-core/src/flows/lifecycle.rs
rust/dl/src/commands.rs
rust/dl/src/render.rs
Integrate copy lifecycle handling into workspace deletion and preserve provenance at the sweep occasion.
  • Drop a copy after a successful delete sweep and retain it after refusal.
  • Distinguish live devpod-result sweeps from kept-copy sweeps in notices and purge reporting.
  • Maintain purge ordering so workspace volume cleanup precedes cache deletion.
rust/devlaunch-core/src/flows/lifecycle.rs
rust/dl/src/commands.rs
rust/dl/src/render.rs
Add structural and behavioral regression coverage for volume-name provenance and reclamation.
  • Guard that only the kept-copy module constructs volume names and only the lifecycle sweep reaches Docker volume removal.
  • Cover copy persistence, malformed records, cache isolation, launch ordering, race handling, plan/act revalidation, refusal retries, delete cleanup, and exact Docker argv.
  • Update public API snapshot entries for the new flows surface.
rust/devlaunch-core/tests/volume_names.rs
rust/devlaunch-core/src/flows/kept_copies.rs
rust/devlaunch-core/src/flows/launch.rs
rust/devlaunch-core/src/flows/lifecycle.rs
rust/devlaunch-core/public-api.rest.txt
Document the new prune behavior, cache considerations, and out-of-scope orphan policy.
  • Describe kept copies, attribution constraints, refusal behavior, and scratch-cache safety.
  • Update CLI documentation and unreleased changelog entries.
  • Clarify that the previously measured 39 orphaned volumes remain untouched.
docs/cleanup.md
docs/development.md
README.md
CHANGELOG.md

Assessment against linked issues

Issue Objective Addressed Explanation
#456 Persist devlaunch's own per-workspace copy of the substituted volume names after every completed up, using the parsed devpod substitution record and leaving incomplete up operations without a copy.
#456 Extend --prune to enumerate kept copies, reclaim volumes only for workspaces no longer listed by devpod, preserve copies on refusals, remove them only after successful removal, and maintain safe ordering and provenance guarantees.
#456 Update the relevant user and developer documentation, including the changed --prune behavior, scratch-cache warning, and continued exclusion of the pre-existing orphaned volumes.

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 Aug 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.10050% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.61%. Comparing base (bf64dd6) to head (4f4c759).

Files with missing lines Patch % Lines
rust/dl/src/render.rs 48.93% 24 Missing ⚠️
rust/devlaunch-core/src/flows/kept_copies.rs 97.38% 7 Missing ⚠️
rust/devlaunch-core/src/flows/lifecycle.rs 98.41% 5 Missing ⚠️
rust/devlaunch-core/src/flows/launch.rs 98.08% 3 Missing ⚠️
Additional details and impacted files
Flag Coverage Δ
python 42.98% <ø> (ø)
rust 95.91% <95.10%> (-0.02%) ⬇️

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

Components Coverage Δ
shipped code (rust) 95.91% <95.10%> (-0.02%) ⬇️
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.

@blooop

blooop commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

APPROVE (fresh-context review by the orchestrating session, which did not write this; GitHub refuses a formal approval from the author's own account, so it lands as a comment).

Four hazards hunted, none survived:

  • The no-pattern rule holds by construction. Every name still originates in a substitution devpod performed and wrote down; what volume_names composes is this repository's own devcontainer template and the docker-in-docker feature's, spelled in exactly one place with tests/volume_names.rs holding the crate to it. That is the same composition the shipped delete-time sweep already does, so Remove the workspace's pixi and dind volumes on delete #325's rule is untouched. The blank-field case is real and tested: empty substitutions name nothing, so -pixi and dind-var-lib-docker- are unreachable as names.
  • The stopped-workspace reading, which would have been blocking. If devpod list omitted stopped workspaces, "no listing names it" would have meant "every stopped workspace", and this would reclaim live volumes. Refuted by measurement on this machine rather than by reading: devpod list returned eight workspaces while docker ps showed two running containers, so a stopped workspace is listed and is never a candidate.
  • TOCTOU across plan and act. The precondition is re-asked under the acting pass's own second listing, ListedAgain is a keep reason with a test behind it, and forget fires only on VolumeSweep::Removed, never on a refusal, so a retry survives and a held volume keeps its copy.
  • Scratch-cache safety (R1). KeptCopies::under(cache_dir) takes the resolved cache rather than resolving its own, so a run pointed at a scratch XDG_CACHE_HOME finds no copies, names no volume and removes none. By construction, not by guard.

On the one unilateral call, widening api::workspace_delete with the copy store: agreed, and for the reason given. Deriving the cache from repos_dir.parent() would be the second answer that can disagree, and moving the drop into the binary would put policy there. It is one declaration, its only caller in this repository is dl (#266: wf links none of core), and it is named in the PR body as the snapshot rule requires. #410 is in build and removes this function; its builder has been told to carry the copy-drop on the same proof rather than redesign the seam.

Non-blocking, recorded rather than asked for: --purge's ordering is documented but not test-pinned, which the PR body already says and justifies (reversing it is not observable through the runner seam).

blooop added 3 commits August 29, 2026 20:34
A workspace deleted by a bare `devpod delete` outside `dl` leaves its two
named volumes standing and takes the only record of their names with it.
devlaunch#325 reads those names out of devpod's `workspace_result.json` at
delete time, which closed the leak for every delete that goes through `dl`
and did nothing for the ones that do not.

So `dl` now takes a second read of the same document at the tail of every
completed `up`, before the provisioning pass, and keeps it as a per
workspace file under its own cache beside the tool verdict markers.
`dl --prune` reclaims from those copies: the domain is the set of copies
rather than a clone walk, and the one precondition per copy is that no
workspace `devpod list` returns carries that id.

Nothing is synthesized. Every name that reaches `docker volume rm` still
came out of a substitution devpod performed and wrote down, at whichever of
the two reads, and `tests/volume_names.rs` holds the crate to that by
construction: one module builds a name, one call site removes one.

A copy can be wrong in exactly two ways and neither is caught by trust. An
already gone volume is a `--force` no-op; a held one is a docker refusal,
reported, with the copy kept so the retry survives. A copy is dropped once,
on proof: a removal that came back removed for a workspace devpod does not
list.

The 39 orphaned volumes measured on the reference host stay. No route
reaches them that is not the pattern devlaunch#451 refuses.

Closes #456.
`docs/cleanup.md`'s "the `--prune` half of the pair still frees no volume at
all" is rewritten rather than deleted: prune still deletes no workspace, and
because it deletes none, the volumes it reclaims belong to workspaces
something else already deleted. The new section beside it says where the copy
lives, what makes it safe to be wrong, and that the 39 measured orphans stay.

`docs/development.md` gains the sentence the scratch-cache convention needs:
a scratch run that creates a workspace writes its copy into the cache it then
throws away.

`public-api.rest.txt` is hand-edited. This container has neither prerequisite
`scripts/public-api-snapshots.sh` needs, which docs/development.md names as
exactly this case: provisional until CI's public-api job agrees. Nothing
promised moved, so `public-api.api.txt` is untouched.
Three things this container could not check for itself, all of them named in
docs/development.md as exactly that case.

`SweepOccasion` sorts *after* `StopOutcome`, not before: ordering is not a
partition property, so nothing local catches a block written in the wrong
position and only CI's regeneration can say. Taken from its diff verbatim.

`public-api.api.txt` moves too, and it is a change to the **promised** surface
rather than a routine one: `api::workspace_delete` gains the copy store as its
sixth parameter, because a delete that sweeps a workspace's volumes is also the
moment its copy is provably pointless. One re-exported declaration, and its only
caller in this repository is `dl`.

And codespell prefers "unparsable".
@blooop
blooop force-pushed the wayfinder/devlaunch-456 branch from 1be803a to 4f4c759 Compare August 29, 2026 19:35
@blooop
blooop merged commit f3e1acb into main Aug 29, 2026
15 checks passed
@blooop
blooop deleted the wayfinder/devlaunch-456 branch August 29, 2026 19:38
blooop added a commit that referenced this pull request Aug 29, 2026
This branch said "volumes are not part of that loss" and gave the
reason: the two names are read from devpod's own workspace_result.json
under DEVPOD_HOME, which a purge does not touch. That was true when it
was written and stopped being true underneath it when #516 merged, which
puts dl's own copy of those names under the cache the purge removes.

It is the case #452 predicted the sentence would have to cover: --purge
never deletes the foreign workspaces, so it destroys the copies naming
their volumes while leaving the volumes standing. What is unchanged is
the ordinary route, and the sentence now separates the two: `dl <ws> rm`
still takes a survivor's volumes with it for as long as devpod lists the
workspace, because that read is devpod's record and not the copy. The
reach a purge costs is --prune's, which reclaims from the copy after
devpod has forgotten a workspace, so a survivor deleted with a bare
devpod delete afterwards leaves volumes nothing can name.

The doc comment, the docs page and the CHANGELOG entry say the same
thing, and the two goldens move with the line.
blooop added a commit that referenced this pull request Aug 29, 2026
This branch said "volumes are not part of that loss" and gave the
reason: the two names are read from devpod's own workspace_result.json
under DEVPOD_HOME, which a purge does not touch. That was true when it
was written and stopped being true underneath it when #516 merged, which
puts dl's own copy of those names under the cache the purge removes.

It is the case #452 predicted the sentence would have to cover: --purge
never deletes the foreign workspaces, so it destroys the copies naming
their volumes while leaving the volumes standing. What is unchanged is
the ordinary route, and the sentence now separates the two: `dl <ws> rm`
still takes a survivor's volumes with it for as long as devpod lists the
workspace, because that read is devpod's record and not the copy. The
reach a purge costs is --prune's, which reclaims from the copy after
devpod has forgotten a workspace, so a survivor deleted with a bare
devpod delete afterwards leaves volumes nothing can name.

The doc comment, the docs page and the CHANGELOG entry say the same
thing, and the two goldens move with the line.
blooop added a commit that referenced this pull request Aug 29, 2026
`workspace_remove` now takes the volume-copy store #516 added to the delete
and passes it straight through, so the fold keeps the reclamation rather
than dropping it: a removal that came back removed still forgets the copy
that named the volumes docker has just taken away. `api` re-exports
`KeptCopies` with it, because a parameter type outside the promise is a
promised call nobody outside can make.

Also: the repo_manager import list is the union of both sides, `main`'s new
`FetchRepoError` beside this branch's `TreeSweep`; one unit test from #516
follows `DeleteOutcome` to its new name; and the CHANGELOG entry sits under
the current `[Unreleased]`, not inside the 0.25.0 section the release cut
renamed underneath it.
blooop added a commit that referenced this pull request Aug 29, 2026
No nightly toolchain on this host, so the generator could not be run here:
CI's public-api job regenerates and diffs, and these are the two rows it
named.

`api::LifecycleNotice::VolumesNotRemoved::occasion` is a merge casualty
rather than a stale file. #516 added the field while this branch was moving
the whole notice vocabulary from the rest file into the promise, so the row
was added to a block that no longer existed and lost on the way across.

The second is the re-export's own cost: promising `KeptCopies` makes the
generator render its inherent and derived impls a second time, at the
canonical path, beside the other promised types' impls.
blooop added a commit that referenced this pull request Aug 29, 2026
As pushed, `public-api.api.txt` named `agent_worktrees::Standing` in
`RemovalRefused` and did not promise it: the type has no struct or impl rows in
that file at all. A consumer holding only `api` got a struct with a field whose
type it could not name, which is devlaunch#531's gap and would have been its
third instance after #427 and #516.

Promoting it honestly was the other option and it is not small. `Standing`
reaches `StandingSite`, `Reason`, `Place`, `Blank`, `Subject` and
`NonEmpty<Loss>`, and `agent_worktrees` has over three hundred rows in the
binary-surface snapshot. That is most of a module's internal vocabulary
arriving in the one tier whose worth is being small and stable. So the promoted
shape was either incomplete or far too wide, and rendering at the seam is the
only option that is both complete and narrow. It is the move the `--ls --json`
payload already makes for the wire, at the same boundary and for the same
reason.

`RemovalRefused` now carries a `RemovalGrounds`, which is made of `String`:
`WouldLose`, `CouldNotTell`, or `BothAtOnce`. Three arms rather than two
options, because a standing is non-empty and every reason in it answers one of
the two, so "neither" cannot happen -- and both render sites carried a fourth
arm apologising for being unreachable, which this deletes rather than comments.
`BothAtOnce` is what keeps #446 true across the seam: a refusal still never
picks one of two true things to say.

Nothing inside `flows` changed. `Standing` is exactly as it was, the domain
type still carries the whole standing, and the conversion is a private free
function at the boundary rather than a method -- a public constructor taking a
`Standing` would put it straight back into the promised tier's signatures.

Also removes `Standing::any_unproved`, which this branch added and nothing ever
called. `Standing` is in the residual, so an uncalled reader there is rows a
consumer can bind to for nothing.
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.

Keep devlaunch's own copy of the volume names, and reclaim from it

1 participant