Skip to content

dl --purge deletes only the workspaces devlaunch created - #129

Merged
blooop merged 5 commits into
mainfrom
wayfinder/devlaunch-107
Aug 8, 2026
Merged

blooop merged 5 commits into
mainfrom
wayfinder/devlaunch-107

Conversation

@blooop

@blooop blooop commented Aug 8, 2026 •

Copy link
Copy Markdown
Owner

Closes #107.

purge_all_data iterated everything devpod list returned. devpod's workspace
namespace is shared with every other way a person makes a workspace — a hand-run
devpod up, another tool, an older devlaunch — so dl --purge treated all of it
as devlaunch's to destroy. Measured on the host this was built on, it would have
deleted pythontemplate, a workspace devlaunch did not create, does not track
and cannot recreate.

What "mine" means

The workspace's source is a clone under devlaunch's own cache directory.
Every workspace dl owner/repo[@branch] creates is such a clone
(<cache>/repos/<owner>/<repo>/<id>, handed to devpod up as a path, which
devpod records as the source).

The ticket proposed metadata.json, which also records these ids. Both were
checked read-only against the same host and they agree exactly on the six
live workspaces, so the choice came down to how each fails:

  • metadata.json lives inside the directory a purge removes, and
    purge_all_data only warns when devpod delete fails. One failed delete plus
    one successful purge would leave a workspace no later purge could recognise.
    A source path is read back from devpod every time and outlives the cache it
    names.
  • The record is append-mostly — nothing prunes it. On the reference host it holds
    39 worktree entries for 6 live workspaces.
  • The source predicate states the actual invariant in one sentence: a purge
    removes devlaunch's cache, and the workspaces it deletes are the ones backed
    by
    that cache. Everything left standing keeps working afterwards, because
    nothing a purge touches backs it.
  • It reads no extra state — no MetadataStorage in the purge path.

Conservative in two places on purpose: a git or unrecognised source is never
ours (devlaunch always hands devpod a local path), and the comparison is path
containment, not a string prefix — <cache>-scratch shares six characters with
<cache> and is not inside it. A mutation to str.startswith reds a test.

What it does with the rest

Names them, before it asks:

$ dl --purge
This will remove all devlaunch data:
  - 4 DevPod workspace(s)
  - /home/you/.cache/devlaunch/ (workspace clones, repo caches, completions)

Leaving 2 workspace(s) devlaunch did not create:
  - devlaunch
  - pythontemplate

Are you sure? [y/N]

The report goes with the confirmation rather than after the deletions, which is
where a user deciding whether to say yes can read it. No opt-in "delete
everything anyway" flag: that is the footgun this ticket exists to remove, and
dl <workspace> rm already deletes one workspace on purpose.

The split is a value — a frozen WorkspaceOwnership(mine, foreign) — rather than
a filter applied at the point of deletion. The count the user confirms and the
set that dies read the same object, and the unrecognised arm has somewhere to be
named from instead of being inferred from a lookup miss.

Known, deliberate incompleteness

dl ./path, dl <git-url>, and a config.toml that points repos_dir outside
the cache all produce workspaces this does not recognise, so they survive a
purge. Closing that needs a new record of "workspaces I created" — a new concept
and a schema change — and the repos_dir case is already outside what a purge
removes, so recognising it would delete containers whose clones stayed on disk.
Erring this way costs a user one dl <workspace> rm; the other kind of mistake
costs work that cannot be recovered. All three are named in the output rather
than silently skipped, and in is_devlaunch_clone's docstring.

Verification

Red first, in two stages. With the type and predicate added but purge_all_data
untouched: 7 failed, 12 passed — the 12 predicate/value tests green, every
purge test red, one of them printing Deleting DevPod workspace: pythontemplate
in its captured output. Then green.

Three mutations on the landed code, each reverted:

mutation reds
for ws in owned.mine + owned.foreign 7 tests, the whole purge behaviour
predicate → workspace.source.startswith(str(cache_dir)) the sibling-prefix test and the cache-root test
drop the source != cache_dir guard the cache-root test

Verified on metal, read-only. The real devpod list on the development host,
run through the new code without deleting anything:

cache dir: ~/.cache/devlaunch
would DELETE:
   bencher-test1-pipagito   <- ~/.cache/devlaunch/repos/blooop/bencher/bencher-test1-pipagito
   bencher-main-kivagede    <- ~/.cache/devlaunch/repos/blooop/bencher/bencher-main-kivagede
   devlaunch-main-zovomobo  <- ~/.cache/devlaunch/repos/blooop/devlaunch/devlaunch-main-zovomobo
   devlaunch-t1-vebilote    <- ~/.cache/devlaunch/repos/blooop/devlaunch/devlaunch-t1-vebilote
would LEAVE:
   devlaunch                <- ~/projects/devlaunch
   pythontemplate           <- ~/projects/python_template

That is the ticket's outcome exactly. devpod list reports the same six
workspaces before and after, and ~/.ssh/config is unchanged
(0fcb9aeff46aad62551d9d688f2021f7). dl --purge was never run for real, no
devpod delete was issued, and the e2e suite was not run — per the ticket, this
is proven by unit tests and fakes.

Full default suite 1047 passed, 22 deselected; ruff, ty and pylint 10.00/10.

Tests that were the defect written down

Four existing tests asserted the old behaviour and now assert the new one with
their original intent intact:

  • test_dl.py::test_purge_deletes_all_workspaces and
    ::test_purge_handles_workspace_delete_failure sourced their workspaces from
    /path and a git URL — which is what someone else's workspace looks like, so
    they asserted that --purge deleted other people's work. They now use clones
    under the cache.
  • test_devpod_spawn_counts.py's two purge tests, the same way; DevPodSpawns
    gains a source_root so it can express both kinds, which is now a real
    distinction.
  • The e2e test test_purge_deletes_workspaces asserted that --purge deleted the
    workspace devpod up had just built for it. It is now
    test_purge_deletes_devlaunchs_workspaces_and_leaves_everyone_elses and builds
    two real workspaces with the same command, differing only in where the
    source lives: a clone under the run's scoped cache, and one outside it. It
    asserts the first is deleted and the second is left standing and named.

Metal, by CI

The e2e job runs pixi run test-e2e on ubuntu-latest for every PR, so the one
claim a recorded listing cannot settle — devpod records back the clone path
devlaunch handed it
— is settled by running it. Green: 8 passed, 14 skipped,
5 workspaces created: e2e-test-create, e2e-test-lifecycle, e2e-test-git, e2e-purge-devlaunchs, e2e-purge-hand-made.

That run also found a second, separate defect, now filed as
#131 rather than folded in here.
On the runner dl --purge -y exits 1:

  - 1 DevPod workspace(s)
Leaving 1 workspace(s) devlaunch did not create:
  - e2e-purge-hand-made
Deleting DevPod workspace: e2e-purge-devlaunchs
Error removing <cache>/devlaunch: [Errno 13] Permission denied: '<cache>/devlaunch/repos/blooop/e2e-repo/e2e-purge-devlaunchs'

The workspace half did exactly what this PR is about, and then shutil.rmtree
hit EACCES: the container writes into the bind-mounted clone as vscode (uid
1000) while the runner is uid 1001, and a non-owner cannot chmod a directory to
empty it. Pre-existing, in the cache half whose code this diff does not touch, and
not fixable from inside the process. It went unnoticed because the e2e purge
test's source used to be outside the cache, so no container had ever written
into the cache during a run. The test therefore asserts its own subject and
tolerates that one failure by name; any other non-zero exit still fails it.

AGENTS.md gains the one fact this changes for the manual workflow: the scratch
XDG_CACHE_HOME recipe now genuinely protects --purge, where before no XDG_*
variable could scope it. That closes the gap #106's
review graduated into this ticket, without needing the second option that review
named.

Proposed CHANGELOG entry

No version bump here — several changes are in flight and a bump publishes a
release immediately. For a later release PR to lift:

Changed

  • dl --purge now deletes only the DevPod workspaces devlaunch created — the
    clones it made under its own cache directory — instead of every workspace
    devpod list returns. devpod's namespace is shared, and a workspace you made
    with devpod up, or that another tool made, was being destroyed along with
    devlaunch's own.
  • dl --purge names the workspaces it is leaving behind, before it asks for
    confirmation, and the count it asks you to approve is now the number it will
    actually delete.

Fixed

  • Pointing XDG_CACHE_HOME at a scratch directory now protects dl --purge.
    It never did: devpod list reads ~/.devpod, so a scratch run still saw — and
    deleted — every real workspace on the machine.

Note for anyone who used dl ./path or dl <git-url>: those workspaces open
a source dl did not clone, so --purge cannot tell them from one you made by
hand and now leaves them standing. They are listed in the output; remove one with
dl <workspace> rm.

🤖 Generated with Claude Code

devpod's workspace namespace is shared. `purge_all_data` iterated everything
`devpod list` returned, so a workspace made with a hand-run `devpod up`, by
another tool, or by an older devlaunch was deleted along with devlaunch's own --
measured on a real host, `--purge` would have destroyed `pythontemplate`, which
devlaunch did not create, does not track and cannot recreate.

"Mine" is now: the workspace's source is a clone under devlaunch's own cache
directory. Every workspace `dl owner/repo[@Branch]` creates is such a clone, and
that directory is exactly what a purge removes -- so the workspaces it deletes
are the ones whose source it was going to delete anyway, and everything left
standing keeps working afterwards.

The split is a value, `WorkspaceOwnership`, not a filter applied at the point of
deletion: the count `--purge` asks the user to confirm and the set it deletes
read the same object, and the workspaces devlaunch does not own have somewhere
to be named from. They are named before the confirmation, while saying no is
still an option.

Three existing tests were this defect written down as assertions -- their
workspaces were sourced from paths outside the cache, which is what someone
else's workspace looks like. They now use the shape devlaunch actually creates
and keep their original intent. The e2e purge test asserted that `--purge`
deleted the workspace `devpod up` had just built for it; it now asserts that
workspace is still standing and named.

@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 have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@sourcery-ai

sourcery-ai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Reviewer's Guide

Update dl --purge so it only deletes DevPod workspaces that devlaunch created (based on clone location under its cache directory) and reports foreign workspaces before confirmation; add supporting ownership logic, documentation, and tests around this behavior and XDG scratch cache protection.

Sequence diagram for updated dl --purge workspace deletion flow

sequenceDiagram
    actor User
    participant CLI as _run_cli
    participant LW as list_workspaces
    participant WO as workspace_ownership
    participant Purge as purge_all_data
    participant DevPod as run_devpod

    User->>CLI: dl --purge
    CLI->>LW: list_workspaces()
    CLI->>WO: workspace_ownership(workspaces, cache_dir)
    WO-->>CLI: WorkspaceOwnership(mine, foreign)
    CLI->>CLI: print mine and foreign workspace counts
    CLI->>CLI: print names of foreign workspaces
    alt user confirms purge
        CLI->>Purge: purge_all_data()
        Purge->>LW: list_workspaces()
        Purge->>WO: workspace_ownership(workspaces, cache_dir)
        WO-->>Purge: WorkspaceOwnership(mine, foreign)
        loop ws in owned.mine
            Purge->>DevPod: run_devpod(["delete", ws.id, "--force"])
        end
        Purge->>Purge: remove cache_dir
    else user cancels
        CLI->>User: no data purged
    end
Loading

File-Level Changes

Change Details Files
Limit purge_all_data to deleting only workspaces devlaunch created, and keep cache deletion behavior intact.
  • Introduce workspace_ownership to split DevPod workspaces into devlaunch-owned and foreign based on cache directory
  • Update purge_all_data to compute ownership once from the memoized workspace list and delete only the devlaunch-owned workspaces
  • Retain cache removal logic, but make purge a no-op (with message) when neither owned workspaces nor cache exist
  • Adjust help text for dl --purge to describe deleting devlaunch’s workspaces and caches
devlaunch/dl.py
docs/worktree-backend-spec.md
README.md
Determine devlaunch ownership via source path containment under the cache directory, not via metadata.json or string prefix matching.
  • Add is_devlaunch_clone predicate that recognises workspaces with local sources under the cache dir, excluding the cache root itself and non-local/relative/unknown sources
  • Use pathlib.PurePath.is_relative_to for lexical containment to avoid sibling-prefix issues (e.g. cache-scratch)
  • Document conservative behavior and known incomplete cases (dl ./path, git URLs, external repos_dir) in is_devlaunch_clone’s docstring
devlaunch/dl.py
Expose workspace ownership as an immutable value object to keep deletion and user-facing counts consistent and give foreign workspaces a place to be named from.
  • Add frozen WorkspaceOwnership dataclass with mine and foreign tuples of Workspace
  • Implement workspace_ownership to split a Sequence[Workspace] into mine/foreign while preserving listing order within each arm
  • Update CLI path to use WorkspaceOwnership for both the purge confirmation message and purge_all_data deletion set
devlaunch/dl.py
test/unit/test_purge_ownership.py
Enhance the dl --purge CLI UX to report foreign workspaces before confirmation, aligned with what will actually be deleted.
  • In _run_cli, compute ownership once and print the count of owned workspaces alongside the cache dir
  • When foreign workspaces exist, print a separate section listing their IDs and indicating they were not created by devlaunch
  • Ensure skip-confirm (-y) still uses the same ownership snapshot so printed counts and deletion targets cannot diverge
devlaunch/dl.py
Adjust unit, integration, and e2e tests to reflect the new ownership-based purge behavior and to pin it against a recorded devpod listing.
  • Add test/unit/test_purge_ownership.py with recorded devpod list JSON, fixtures, and tests for is_devlaunch_clone, workspace_ownership, purge_all_data, and main(['--purge']) with/without foreign workspaces and scratch caches
  • Update TestPurgeFunctionality unit tests to construct Workspaces as cache-based clones instead of arbitrary paths/git sources, and to assert deletion of devlaunch-owned workspaces only
  • Modify E2E purge test to assert that a workspace devlaunch did not create survives purge and is explicitly named in output, rather than being deleted
  • Update DevPodSpawns fake to track a source_root so purge tests can simulate devlaunch-owned vs foreign workspaces via source paths, and adjust purge-related tests accordingly
test/unit/test_purge_ownership.py
test/test_dl.py
test/e2e/test_full_workflow.py
test/test_devpod_spawn_counts.py
Document the purge semantics and scratch cache behavior for users and agents.
  • Extend README with a "What purge deletes" section describing shared devpod namespace, devlaunch-owned clone layout, examples, and behavior for dl ./path and dl
  • Update AGENTS.md to state that pointing XDG_CACHE_HOME at a scratch cache now protects dl --purge by making all real workspaces unrecognised and thus preserved
  • Align worktree-backend-spec.md wording with the new behavior: purge deletes only devlaunch-created workspaces and names others
README.md
AGENTS.md
docs/worktree-backend-spec.md

Assessment against linked issues

Issue Objective Addressed Explanation
#107 Change dl --purge so that it deletes only DevPod workspaces that devlaunch created, and no longer deletes workspaces it did not create. ✅
#107 Define and implement explicit behavior for workspaces devlaunch did not recognize ("foreign" workspaces), including distinguishing them in code and reporting them to the user instead of deleting them. ✅
#107 Update tests and documentation to reflect and enforce the new purge behavior, ensuring safe test harnesses and accurate user-facing descriptions of what --purge deletes and what it leaves. ✅

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 8, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.21%. Comparing base (088efe6) to head (47828a3).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #129      +/-   ##
==========================================
+ Coverage   92.76%   93.21%   +0.44%     
==========================================
  Files          18       18              
  Lines        2005     2018      +13     
==========================================
+ Hits         1860     1881      +21     
+ Misses        145      137       -8     
Files with missing lines Coverage Δ
devlaunch/dl.py 92.12% <100.00%> (+1.04%) ⬆️
devlaunch/worktree/config.py 92.85% <100.00%> (-0.90%) ⬇️
devlaunch/worktree/storage.py 97.74% <100.00%> (+0.53%) ⬆️
devlaunch/xdg.py 100.00% <100.00%> (ø)

Impacted file tree graph

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Austin Gregg-Smith added 3 commits August 8, 2026 14:21
The e2e job on ubuntu-latest runs `pixi run test-e2e` on every PR, so this test
is measured on a real devpod rather than left unverified. It now builds two real
workspaces with the same command, differing only in where the source lives -- a
clone under devlaunch's scoped cache, and one outside it -- and asserts `--purge
-y` deletes the first and names the second as one it did not create.

That is the claim the whole predicate rests on: what devlaunch creates is a clone
under its own cache, and devpod records that path back as the workspace source.
It is the one part of this change a recorded listing cannot prove.
CI's e2e run reported `assert 1 == 0` with the reason truncated out of the
repr. `--purge` says on stdout what it deleted, what it left and what it could
not remove, so a bare rc comparison is the one thing that cannot be read.
…eleted

Spec, blocking: the e2e test could not pass. `theirs = "e2e-test-purge"` is a
substring of `mine = "e2e-test-purge-mine"`, so `"Deleting ... e2e-test-purge"
not in stdout` failed on *correct* output while `theirs in stdout` passed on no
evidence -- one bug making one assertion impossible and its neighbour vacuous.
The ids no longer contain one another, and both assertions match whole lines, so
a substring cannot stand in for a fact again.

Standards, blocking: the source-type guard had a surviving mutant. Deleting `if
workspace.source_type != "local"` left all 1047 tests green, because the two
tests naming that branch used a git URL and `"{}"` as their sources -- neither is
an absolute path, so containment already rejected them and the guard was never
reached. The test now uses a source *inside* the cache directory with a `git` or
`unknown` type, which is the only shape that puts the guard under test, and it
asserts the same path is recognised when local so the setup cannot rot.

Also from Standards: `_get_cache_dir`, `_get_cache_base` and
`_get_default_metadata_path` were three byte-identical spellings of the same
directory, and this change made them load-bearing -- `--purge` now reads the
first to decide which workspaces are devlaunch's, and the other two are what put
those clones on disk. They now share one answer in `devlaunch.xdg`, beside
`config_home`, which exists for this exact reason. `workspace_ownership` makes
one pass instead of asking the predicate twice per workspace, pinned by a test.
The frozen-dataclass test is gone: `ty` already rejects the assignment, so it
tested CPython.

The e2e purge test no longer requires rc 0. On a runner the container writes into
the bind-mounted clone as uid 1000 while the suite runs as 1001, so `rmtree` hits
EACCES on a directory it cannot chmod -- measured in CI, not inferred. That is a
pre-existing defect in the cache half of the purge, unrelated to ownership and
not fixable from inside the process; the test asserts its own subject and
tolerates only that one failure, by name.

Finally, a guard restored rather than authored: commit 1e72e14 accidentally
captured a review subagent's live mutation of a shared worktree, deleting the
source-type guard from the tree. It is back, and its mutant is dead.
@blooop

blooop commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

This was generated by AI during review.

Two independent axes, run as parallel subagents in fresh context against the fixed point b09d37dc398562d05ad543cd5da9ab08f597b87f...HEAD. Neither had seen the code being written. Findings are not merged or reranked across axes.

Standards

Blocking — surviving mutant on the source-type guard (devlaunch/dl.py). Deleting if workspace.source_type != "local": return False left all 1047 non-e2e tests green. The two tests that named the branch — test_a_git_sourced_workspace_is_not and test_a_workspace_with_no_source_at_all_is_not — passed for the wrong reason: their sources (https://github.com/o/r, "{}") are not absolute paths, so is_relative_to already rejected them and the guard was never reached. The only shape that exercises the guard is a git/unknown workspace whose source is an absolute path under the cache directory.

Should-fix — three byte-identical copies of the cache directory. dl._get_cache_dir, worktree/config._get_cache_base and worktree/storage._get_default_metadata_path all spelled out the same directory. This diff makes them load-bearing rather than merely duplicated: --purge reads the first to decide what is devlaunch's, and the other two are what put those clones on disk, so a copy that drifted would make a purge silently stop recognising its own work.

Should-fix — the _get_cache_dir() vs repos_dir choice is defensible, verified. The reviewer checked rather than accepted the author's argument: purge_all_data only rmtrees cache_dir, so deleted workspaces and deleted sources genuinely answer to one directory.

Should-fix — test_purge_deletes_all_workspaces lost its git-source case and kept a name saying "all workspaces" while its docstring says "every workspace devlaunch created" (Mysterious Name).

Nits. WorkspaceOwnership earns its place — both arms are consumed, so not Speculative Generality and not a Middle Man — but workspace_ownership evaluated the predicate twice per workspace. test_neither_arm_can_be_rewritten_after_the_fact tested dataclasses.frozen, i.e. CPython.

Passed. The adapted tests in test_devpod_spawn_counts.py and test_dl.py adapt honestly with explanatory comments rather than being weakened; the e2e rewrite strengthens rather than weakens; prose style matches the repo's grain throughout.

Spec

Blocking — the new e2e test could not pass, so the one claim only metal can settle was still unsettled. theirs = "e2e-test-purge" is a substring of mine = "e2e-test-purge-mine", so assert f"Deleting DevPod workspace: {theirs}" not in purge_result.stdout fails on correct output (the real line Deleting DevPod workspace: e2e-test-purge-mine contains it), and assert theirs in purge_result.stdout passes vacuously for the same reason. Against the map's extra principle — "every claim about container/devpod shape is settled by running the thing and looking" — the PR body's claim that this is "measured on a real devpod rather than argued" did not hold.

The rest of the spec is met, checked independently rather than read off the PR.

  • The sub-question the ticket names — "what should --purge do about workspaces it finds but does not recognise?" — is answered with candidate 2, "Delete only recognised ones and report the others by name", printed before the confirmation; candidate 3 is refused with the ticket's own reasoning.
  • The metadata.json deviation stands. The ticket says "devlaunch already records the ones it created in metadata.json" — true but weak, and the author's numbers reproduce exactly: 6 live workspaces, 39 worktree records, 35 stale, and both predicates agree on all six. The deviation was recorded on dl --purge deletes workspaces devlaunch never created #107 before the build, so it contradicts nothing on the map.
  • Principle 3 — "a distinction the code should be able to state, not something inferred from a lookup miss at the point of deletion" — is met by the frozen WorkspaceOwnership; the count printed and the set deleted read one object.
  • Red-state safety — "a test whose red state deletes real workspaces is not acceptable". None can: unit tests patch subprocess.run, and test/conftest.py scopes DEVPOD_HOME at pytest_configure, which the e2e purge subprocess inherits.
  • Metal — the real listing run through the new code read-only deletes the four cache clones and leaves devlaunch and pythontemplate, exactly as claimed.

Should-fix. CHANGELOG.md has an [Unreleased] section; writing there bumps nothing, so "No version bump here" does not by itself explain the omission.

Nit. README named two survivors where is_devlaunch_clone's docstring names three — a config.toml pointing repos_dir outside the cache is the missing one.

Nit. Containment is lexical, so a symlinked ~/.cache could make devlaunch's own clones read as foreign if devpod records a resolved path. Errs safe, survivors are named, unverified.


Process defect found during the review, and it is the reviewer's

A Standards subagent ran a mutation experiment inside the shared worktree while the author was committing, and commit 1e72e14 swept up the live mutation — deleting the source-type guard from the branch. Caught because the subagent reported it, not because anything else would have. A review that mutates code must do it in its own worktree. The guard has been restored, and its mutant is now dead.

Verdict

Request changes — posted as a comment because GitHub refuses --request-changes on a self-authored PR; the written verdict is what the gate reads.

Blocking, both now addressed at 48bb714:

  1. Spec — the e2e substring bug. Ids no longer contain one another (e2e-purge-devlaunchs / e2e-purge-hand-made) and both assertions match whole lines, so a substring cannot stand in for a fact again.
  2. Standards — the surviving source-type mutant. The test now uses a source inside the cache with a git/unknown type, and asserts the same path is recognised when local so the setup cannot rot. Mutation re-run: the guard's deletion now reds two tests where it previously reded none.

Should-fixes taken: the three cache-directory copies now share one answer in devlaunch.xdg beside config_home; workspace_ownership makes one pass, pinned by a test; test_purge_deletes_all_workspaces renamed; README names all three survivors; the frozen-dataclass test deleted, since ty already rejects the assignment.

Should-fix declined, with the reason: the CHANGELOG entry stays in the PR body. This run is under an explicit batching policy — several changes are in flight and a release PR will lift the prose — so touching CHANGELOG.md is deliberate scope this PR does not take.

Nit declined: the symlinked-~/.cache case is left unverified rather than guessed at. Resolving it means a real devpod up through a symlinked cache; it errs safe and the survivors are named, so it is worth a ticket rather than a speculative resolve().

Full mutation battery on the corrected code, all five dead:

mutation reds
delete the source_type guard test_only_a_local_source_can_be_devlaunchs[git], [unknown]
predicate → source.startswith(str(cache_dir)) the sibling-prefix test, the cache-root test
drop the source != cache_dir guard the cache-root test
purge iterates owned.mine + owned.foreign 7 tests, the whole purge behaviour
partition asks the predicate twice test_the_predicate_is_asked_once_per_workspace

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.

dl --purge deletes workspaces devlaunch never created

1 participant