Skip to content

Reclaim the images devpod's record names #458

Open

Description

@blooop

Work

Reclaim the images devpod's record names, on the two occasions the images decision settled.

Split from #456 deliberately. That ticket owns the marker: the per-workspace record devlaunch keeps of what devpod substituted, widened by the images decision to carry the image reference alongside the two volume names, so one small file is not versioned twice. This ticket owns the reclaim that reads it.

Scope, per section 6 of the resolution on #450:

  • The parse, and the RecordedImage / ReclaimableImage shapes. MergedConfig.image non-null means the devcontainer named the reference (alpine:3.20) and it is never devlaunch's to touch; null with config.build means devpod derived it. Two arms, and the Derived arm is the only constructor for a removable reference.
  • Delete-time removal, unforced, beside the existing volume sweep. A refusal degrades the reference into the prune candidate set rather than failing the delete, exactly as the volume sweep cannot fail a delete.
  • The --prune candidate predicate: a recorded reference that is not the current reference of a workspace devpod lists. This is the clause that reaches the rebuild orphans, and it does so without a pattern, because the tag was named by a real record when it was current.
  • Drop-on-proof: a reference proved gone leaves the marker, so the candidate set does not grow without bound.
  • clients/docker.rs gains remove_image and no listing verb. The absence is the point: with nothing that enumerates images, a pattern sweep has no expression. The module header currently says nothing here removes an image and must be replaced with the argument that now holds, not merely edited.
  • Print no image byte figures anywhere, including dl --ls --size. Per-image bytes are not additive across shared layers, so any figure would be a number that reads as a size and is not one.
  • Seven tests, including the refusal-to-guess asserted by absence of a path rather than by a guard firing.

Two things this ticket must not quietly do. It does not reclaim the existing 86.5 GB, the images a never-completed create left, or devpod build leftovers: those were refused as a design and the docs must say so. And it does not touch the build cache, which got its own verdict on its own argument.

Unforced docker image rm refuses while any container refers to the image, running or stopped (measured, docker 29.7.2). Say in the code whether the design leans on that, because it is a safety property nobody had to build.

Activity

  1. blooop commented on Sep 4, 2026

    @blooop
    OwnerAuthor

    Two measurements on the record this ticket's reclaim reads, from dl 0.29.0 today. Both cut against the record being trustworthy as-is, and the first suggests the volume half wants the same two-arm shape you settled for images.

    1. The recorded volume list names a volume that was never created, and omits the two that were.

    A successful launch of kinisi-robotics/team-tracker@dl-guard-probe wrote ~/.cache/devlaunch/workspace-copies/team-tracker-dl-guard-probe-p3ms.json:

    {"volumes":["team-tracker-dl-guard-probe-p3ms-pixi","dind-var-lib-docker-default-te-f629d"],
     "image":"team-tracker-dl-guard-probe-p3ms-3ab7a:devpod-f6e10b21374b46ed2ee0cd3142d5d189",
     "declared_image":null}

    What devpod actually mounted, from the same launch's docker run:

    type=volume,src=team-tracker-pixi
    type=volume,src=team-tracker-node-modules
    type=volume,src=dind-var-lib-docker-default-te-f629d
    

    docker volume inspect team-tracker-dl-guard-probe-p3ms-pixi → no such volume. It was never created.

    The pattern (inferred, not confirmed against the source): where the devcontainer templates a name, devpod substitutes and the record matches — the dind mount is declared dind-var-lib-docker-${devcontainerId} and is recorded correctly. Where the devcontainer names a literal volume, devpod mounts the literal, but the record holds a synthesised <workspace-id>-<suffix> name that nothing created. The image arm is fine (declared_image: null with a build, exactly as section 6 describes).

    Consequence for the reclaim specced here: for a literal-named volume it will try to remove a name that does not exist, and will never reach the ones that do. Drop-on-proof makes that invisible — a phantom proves gone on the first pass and leaves the marker, so the candidate set stays clean while the real volumes accumulate untouched.

    And the same distinction you drew for images applies to volumes. team-tracker-pixi is declared by that repo, with a deliberately fixed name, and is shared across every workspace of the repo on purpose — it is the warm cache that makes a second launch 21.6s instead of 5m36s. Reclaiming it on workspace delete would be wrong for the same reason MergedConfig.image non-null is never devlaunch's to touch. A recorded volume needs a Declared / Derived split, with Derived the only constructor for a removable name; otherwise a correct implementation of this ticket deletes another repo's shared cache.

    (Worth knowing why that repo's fixed names are load-bearing here: they are also a live bug on their side — a per-workspace mount target under a fixed-name volume bakes one workspace's absolute path into every console script and breaks the next branch. That is theirs to fix, not devlaunch's, but it is why the volumes are shared and why deleting them has a visible cost.)

    2. No record is written at all when the launch fails in the lifecycle hook.

    Observed once, so treat it as a lead rather than a result: a team-tracker launch that died in postCreateCommand left the workspace registered in --ls with its container running, and no workspace-copies/<ws>.json. The successful probe above did get one.

    If that holds, every record-driven reclaim misses failed launches by construction — and failure is the case that leaks most. Measured against the same removal path: after a successful launch rm reclaimed dind-var-lib-docker-default-te-f629d; after a failed one it left the dind volume behind. The record's presence is the obvious candidate for that difference.

    Cheap fixture for both: assert the recorded names against the container's actual mounts for a devcontainer that declares one literal and one templated volume, and assert a record exists after a launch whose postCreateCommand exits non-zero.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions