Skip to content

Retire repos_dir - #481

Merged
blooop merged 2 commits into
mainfrom
wayfinder/devlaunch-467
Aug 25, 2026
Merged

Retire repos_dir#481
blooop merged 2 commits into
mainfrom
wayfinder/devlaunch-467

Conversation

@blooop

@blooop blooop commented Aug 25, 2026

Copy link
Copy Markdown
Owner

Closes #467. Carries out the decision on #460: worktree.repos_dir comes out of config.toml, WorktreeConfig and the public surface, nothing on disk is moved or deleted, and the whole of the migration is one line on stderr.

The point is the completeness, not the smaller struct

is_devlaunch_clone is a single containment test against the cache directory, and layout_of_clone is gated on the same answer, so one predicate decides devlaunch, disk, unsaved, the SIZE column and the picker's owner/repo grouping together. It was only ever sound if nothing could put one of dl's own clones outside that directory, and repos_dir could.

So the clone root is now a function of the cache directory and of nothing else:

  • xdg::clone_root_in(cache) -> cache.join("repos"), in the module whose doc already exists to keep the cache dir, the clone root and metadata.json's path from drifting. Unifying the spelling left one way to drift anyway; this closes it.
  • The clone manager's only public constructor is WorkspaceCloneManager::in_cache(cache_dir, config, git). It takes the cache directory, never a root, and derives the root itself. new still takes an arbitrary root and is pub(crate), which is what the tests drive; no production path can reach it.
  • WorktreeConfig carries no paths at all now, so a configuration cannot express placement.

The binary passes in_cache the same cache_dir it decides ownership with, so the two cannot be built from different answers. A clone of dl's own that the listing reads as someone else's is unrepresentable, for a given cache home. (A moved XDG_CACHE_HOME still strands what the old one held, as it does for any tool that respects it. That is not this key's doing.)

The two reds

1. The completeness claim (flows::listing, every_clone_a_configuration_can_place_is_one_this_recognises). A configuration naming a root outside the cache, the clone directory a launch would derive from it, and the assertion that the listing recognises it:

thread 'flows::listing::tests::every_clone_a_configuration_can_place_is_one_this_recognises'
panicked at devlaunch-core/src/flows/listing.rs:1762:9:
a clone dl placed reads as someone else's: /somewhere/else/blooop/devlaunch/devlaunch-main-3j1t

The test survives the change with only its construction line altered, and it now also asserts measurable_clone, owner_of and repo_of on the same path, which are the three answers that degraded together.

2. The notice (test/unit/test_config_retired_keys.py), judged from outside through the shipped binary, because "the user is told" is a claim about what a run prints:

    def test_the_directory_it_named_is_printed(self, devpod_shim):
        write_config(f'[worktree]\nrepos_dir = "{CONFIGURED}"\n')
        result = run_dl(devpod_shim, "--ls", "--json")
        assert result.returncode == 0, result.stderr
>       assert "repos_dir" in result.stderr
E       AssertionError: assert 'repos_dir' in ''
E        +  where '' = CompletedProcess(args=['.../release/dl', '--ls'], returncode=0,
E                       stdout='No workspaces found.\n', stderr='').stderr

What the notice says, and where it fires

config.toml still sets worktree.repos_dir = '/srv/devlaunch-clones'. dl no longer reads it:
clones live under dl's cache directory, and XDG_CACHE_HOME is what moves that. Nothing in
that tree was moved or removed, so it is yours to keep or delete.

Core carries the typed fact (config::RetiredKey::ReposDir { named }) and the binary writes the sentence, like every other notice here. It rides with dl's records, so every command that opens the cache says it once: a launch above all, plus --ls --json, --prune, --reconcile. dl --ls's table stays silent, deliberately, because it reads devpod and nothing else and that one-round-trip property is worth more than a second place to say this.

Two calls worth flagging for review:

  • It fires whenever the key is present, not only when it resolves somewhere other than <cache>/repos as Is repos_dir supported or vestigial #460 wrote it. Deciding "somewhere other" means expanding ~ and comparing against a derived default, and that comparison is the thing that can be silently wrong: a symlinked home, a cache home that has moved since the tree was made. Wrong in that direction is silence about a tree nothing else will ever name, which is the one outcome principle 1 rules out. A user whose value happens to equal the default is told to delete a dead key, which is true and costs one line. This is also what lets expand_tilde die as the ticket asked: the value is quoted back verbatim, resolved by nothing.
  • refresh_cache no longer loads the config at all (its only use of it was repos_dir), so --install, --refresh and --completion-data no longer refuse on an unreadable config.toml. Consequence rather than goal, and the good direction: a malformed config should not stop completions being installed.

Snapshots

Hand-edited into public-api.rest.txt, since the container has the pinned cargo-public-api but no nightly rustdoc backend; CI's regenerate-and-diff is what proves the rendering right. Four changes, all on the binary surface and none in public-api.api.txt:

  • WorktreeConfig::repos_dir removed.
  • config::RetiredKey added, with its derived impls.
  • worktree_config() now answers (WorktreeConfig, Vec<RetiredKey>), matching MetadataStorage::open's shape.
  • WorkspaceCloneManager::from_config becomes ::in_cache, taking the cache directory.
  • xdg::clone_root_in added.

Docs

docs/cleanup.md's purge paragraph loses the config.toml case, so what --purge leaves is two things and not three. AGENTS.md's scratch-cache guidance loses the hazard behind XDG_CACHE_HOME: one variable now scopes the whole of what dl stores, and test/conftest.py's XDG_CONFIG_HOME scoping is belt-and-braces rather than load-bearing, which its docstring now says. docs/rust-rewrite-plan.md row 9 is untouched: it records a port that happened.

Out of scope

  • Name the sources in --purge's leaving list #461 is unblocked and slightly smaller, and it is not done here. Its first bullet drops "which is devlaunch's own under a moved repos_dir" from the list of workspaces a user cannot tell apart, because there is no such workspace any more; the --ls degradation it deferred to this ticket needed no build work beyond the placement change. Naming the sources in the leaving list is still Name the sources in --purge's leaving list #461's.
  • The other four dead keys. enabled, cleanup.auto_prune, cleanup.prune_after_days and fallback_image are parsed, defaulted, unit-tested and read by nothing outside config.rs. Is repos_dir supported or vestigial #460 recorded that and left it; deciding their fate before the prune unit is decided would be guessing.

Summary by Sourcery

Retire worktree.repos_dir and make the cache directory the single source of truth for clone placement and ownership.

Bug Fixes:

  • Ensure all newly created clones are recognized consistently as devlaunch-owned across listing, sizing, unsaved-state, grouping, and pruning operations.

Enhancements:

  • Retire the configurable worktree.repos_dir setting and derive the clone root exclusively from the cache directory.
  • Report retained repos_dir configuration values once per cache-opening command without moving or deleting existing clone trees.
  • Simplify configuration loading and clone-manager APIs by removing configurable path handling and related directory creation and tilde expansion.
  • Allow completion installation, refresh, and completion-data commands to proceed without loading worktree configuration.

Documentation:

  • Update cleanup and scratch-cache guidance to reflect cache-derived clone placement and retired repos_dir behavior.

Tests:

  • Add core and end-to-end coverage for clone ownership completeness and retired-key notices.

Chores:

  • Update the public API snapshot for the retired configuration and revised clone-management interfaces.

`worktree.repos_dir` is out of `config.toml`, `WorktreeConfig` and the
public surface. It had no writer, no documentation, no test that
exercised a non-default value, and `XDG_CACHE_HOME` has always been the
supported way to move the cache. It also had authority over where clones
went, and that is the part worth the change.

The clone root is now `xdg::clone_root_in(cache_dir)` and the only
production constructor for a clone manager takes the cache directory
rather than a root. So every clone dl makes is inside the directory
`is_devlaunch_clone` tests against, by construction: the
`devlaunch: false` / `SIZE -` / `unsaved: null` degradation on one of
dl's own clones has no representation any more, rather than a second
predicate to guard it with.

The migration is one line on stderr. A config still naming the key gets
the directory quoted back, verbatim, with a pointer at XDG_CACHE_HOME
and the assurance that nothing at that path was moved or removed. Not an
error, because a stale file is not punished here; not silence either,
because silence is what strands a tree. Fired whenever the key is
present rather than only when it resolves somewhere other than the
cache: resolving is what can be silently wrong, and wrong there means
saying nothing about a tree nothing else will name.

Loading a configuration is now pure but for the read: `ensure_repos_dir`
and `is_ours_to_create` go with the key they existed for, and so does
`expand_tilde`, since nothing resolves the value any more.

Closes #467

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

@blooop blooop mentioned this pull request Aug 25, 2026
@sourcery-ai

sourcery-ai Bot commented Aug 25, 2026

Copy link
Copy Markdown

Reviewer's Guide

Retires worktree.repos_dir end to end: clone placement is now canonically derived from the cache directory, ensuring ownership and listing predicates remain complete, while stale configurations produce one actionable non-fatal stderr notice and no disk contents are moved or removed.

Sequence diagram for retired repos_dir reporting

sequenceDiagram
    participant Config as config.toml
    participant Core as Core config loader
    participant Session as open_records
    participant Binary as dl report
    participant User as stderr

    Config->>Core: worktree_config()
    Core-->>Session: WorktreeConfig, RetiredKey::ReposDir
    Session->>Binary: report(records)
    Binary->>User: retired_keys()
    User-->>User: One non-fatal migration notice
    Note over User: Notice names the configured path and says nothing was moved or removed
Loading

File-Level Changes

Change Details Files
Derive the clone root exclusively from the cache directory and remove configurable placement.
  • Add xdg::clone_root_in(cache) as the canonical <cache>/repos path.
  • Replace the public config-based clone-manager constructor with in_cache(cache_dir, config, git); retain arbitrary-root construction for internal tests.
  • Remove repos_dir and all tilde expansion and directory-creation behavior from WorktreeConfig loading.
  • Update ownership, listing, pruning, migration, completion-cache, and launch paths to use the derived root.
  • Add coverage proving every clone placement is recognized consistently by ownership, measurement, and grouping logic.
rust/devlaunch-core/src/domain/config.rs
rust/devlaunch-core/src/domain/xdg.rs
rust/devlaunch-core/src/flows/workspace_clone.rs
rust/devlaunch-core/src/flows/listing.rs
rust/devlaunch-core/src/flows/lifecycle.rs
rust/devlaunch-core/src/flows/launch.rs
rust/dl/src/session.rs
rust/dl/src/commands.rs
Report retired worktree.repos_dir settings as a non-fatal, once-per-command migration notice.
  • Carry the verbatim configured value through RetiredKey::ReposDir without resolving or comparing it.
  • Render a single stderr message explaining that clones now live under the cache, XDG_CACHE_HOME controls relocation, and no files were moved or deleted.
  • Attach retired-key reporting to cache records so cache-opening commands emit it while dl --ls remains silent.
  • Add Rust and shipped-binary tests for content, once-only behavior, maintenance commands, stale-config tolerance, and absence of notices when unset.
  • Stop loading configuration during cache refresh, allowing install, refresh, and completion-data flows to proceed with unreadable config files.
rust/devlaunch-core/src/domain/config.rs
rust/devlaunch-core/src/domain/metadata.rs
rust/dl/src/session.rs
rust/dl/src/render.rs
rust/dl/src/commands.rs
test/unit/test_config_retired_keys.py
Align public API snapshots, documentation, and test-environment guidance with the retired setting.
  • Remove WorktreeConfig::repos_dir and rename/update the public constructor and tuple-returning config loader in the API snapshot.
  • Document that <cache>/repos is the sole clone location and purge no longer leaves a separately configured tree.
  • Update scratch-cache and test isolation guidance to reflect that XDG_CACHE_HOME now scopes all devlaunch-owned data.
rust/devlaunch-core/public-api.rest.txt
AGENTS.md
docs/cleanup.md
test/conftest.py

Assessment against linked issues

Issue Objective Addressed Explanation
#467 Remove repos_dir from StoredWorktree and WorktreeConfig, delete expand_tilde, is_ours_to_create, and ensure_repos_dir and their tests, and make configuration loading pure while deriving the clone root from the cache directory and fetch_interval. The PR removes repos_dir from WorktreeConfig and deletes the helper functions, tests, and directory-creation side effect. However, repos_dir remains in StoredWorktree so it can be parsed and reported, contrary to the issue's explicit requirement to drop it from StoredWorktree.
#467 For a config.toml containing worktree.repos_dir outside /repos, emit one non-error notice naming the configured directory and explaining that XDG_CACHE_HOME controls the clone location. The PR implements a non-error, once-per-command notice with the configured value and XDG_CACHE_HOME, but it emits the notice whenever repos_dir is present, including when it equals the default /repos. The issue specifically requires the notice only when the configured value is something other than that default.
#467 Regenerate the public API snapshots and update cleanup documentation while leaving the archival rust-rewrite-plan row untouched.

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 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.81022% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.37%. Comparing base (5cb909a) to head (4720307).

Files with missing lines Patch % Lines
rust/dl/src/commands.rs 60.00% 2 Missing ⚠️
rust/devlaunch-core/src/flows/listing.rs 96.15% 1 Missing ⚠️
Additional details and impacted files
Flag Coverage Δ
python 42.98% <ø> (ø)
rust 95.71% <97.81%> (+<0.01%) ⬆️

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

Components Coverage Δ
shipped code (rust) 95.71% <97.81%> (+<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.

@blooop blooop left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

This was generated by AI during review.

Reviewed at 0e42ba7d5cf9fa7febc572c4145496192634f613 in fresh context, against #467 and the decision on #460. Locally: cargo test --workspace pass, cargo clippy --locked --all-targets -- -D warnings clean, cargo fmt --check clean, pytest test/ 420 passed / 26 deselected. All 15 CI checks green.

Standards

Non-blocking.

  1. test/conftest.py:71-79 now calls the XDG_CONFIG_HOME scoping "belt-and-braces" and justifies keeping it only in the read direction ("keeps a developer's own config.toml out of the suite's assertions"). The same PR adds test/unit/test_config_retired_keys.py:26-31, which writes $XDG_CONFIG_HOME/devlaunch/config.toml. That makes the guard load-bearing again in the write direction: someone acting on the new docstring and dropping the setenv would have the suite overwrite a developer's real ~/.config/devlaunch/config.toml. Nothing is at risk at this commit — the setenv is there. The defect is that the reason now recorded for keeping it no longer covers the strongest reason. One sentence.

  2. rust/devlaunch-core/src/domain/config.rs:232 keeps repos_dir: Option<String>, which is right for detection, but it also keeps the key's power to refuse the whole load. Verified against the release binary: repos_dir = 5 makes dl --ls --json exit 1 with invalid type: integer 5, expected a string, on every records-opening command, over a key dl does not read — and the retired-key notice never prints, because the load it rides on never succeeds. The module doc two paragraphs up states "a stale file is not punished". Not a regression (main behaves the same), so nothing to fix under principle 1; it is a veto with nothing left to protect. The comment claiming "a wrong-typed value refuses the load exactly as it did before" is true but unpinned — the wrong-type loop at config.rs:427 lists three keys and not this one, so relaxing to Option<toml::Value> would go green.

  3. rust/dl/src/session.rs:111session::worktree_config() is now a pure pass-through with exactly one caller, in its own module (session.rs:160); its second caller was deleted here. Middle Man.

  4. rust/devlaunch-core/src/flows/completion_cache.rs:273-277update_completion_cache(cache_dir, repos_dir) is pub and still takes both, so update_completion_cache(cacheA, clone_root_in(cacheB)) compiles. The invariant workspace_clone.rs:334 states ("the caller passes the same cache directory it decides ownership with, so the two cannot be built from different answers") is enforced at in_cache and not at this seam. No production caller does it; commands.rs:381 passes the matched pair.

  5. test/unit/test_config_retired_keys.py:57-63 asserts stderr only, no returncode, unlike its three siblings: a --prune that started refusing would still pass.

  6. Vocabulary: xdg.rs:80 establishes "clone root" while workspace_clone.rs:305, repo_manager.rs and migration.rs keep repos_dir as the internal path name (194 hits under rust/). Grepping repos_dir no longer separates the retired key from the field.

Checked, clean. The tuple return matches MetadataStorage::open exactly. RetiredKey/render::retired_keys mirrors Notice/metadata_notices: typed fact in core, sentence in the binary. clone_root_in belongs in xdg, whose stated job is precisely this non-drift. No dead code, no unused imports. No em or en dash added under docs/; test_docs_prose.py globs README plus docs/*.md only, so AGENTS.md's dashes are out of scope; no docs/ page says "this README".

Spec

Non-blocking. #460 §6 items 1 through 6 are done.

  1. docs/cleanup.md:137 is now wrong for the exact population this PR exists for. "Two things dl does create are in that second list rather than the first." On a machine that had repos_dir set there is a third, and I reproduced it: a seeded workspace under the retired root shows up as Leaving 1 workspace(s) devlaunch did not create. §6.4 asked for the config.toml sentence to go and it went, but the count went with it. "At least two", or keep a clause for the retired key.

  2. Keeping StoredWorktree::repos_dir (config.rs:232) is not a violation of §6.1's "Drop repos_dir from StoredWorktree", despite what the bot review says. §6.1 and §6.2 are in direct tension: StoredConfig does not deny unknown keys, so dropping the field makes the key invisible to serde and the §6.2 notice unbuildable. §6.2 is the load-bearing half. The field is private, reaches no WorktreeConfig, and is documented in place.

  3. The "/custom/repos" test was updated, not deleted as §6's "How it is tested" says (config.rs:298-311). Divergence in letter, better than the letter: that test pins six keys, and deleting it would lose unrelated coverage.

  4. §6.1 asks in_cache to take "the cache-derived root plus fetch_interval"; it takes &WorktreeConfig. Wider than specified, and it buys the cache-dir/ownership coupling the PR is built on.

  5. Snapshot rows verified against the declarations, one by one, since they went in by hand: RetiredKey plus its five impl rows match #[derive(Debug, Clone, PartialEq, Eq)] and the row shape of the file's other derived enums; worktree_config()'s tuple; in_cache(&Path, &WorktreeConfig, Git<'r>) -> Self; clone_root_in(&Path) -> PathBuf; WorktreeConfig::repos_dir removed leaving five alphabetical fields. Sort positions correct (ConfigError < RetiredKey < WorktreeConfig < worktree_config; clone_root_in < config_home < devlaunch_cache). defaults_in -> defaults is pub(crate) and correctly has no row. public-api.api.txt correctly unchanged. CI's regenerate-and-diff passing is the real proof.

  6. Both #467 seams built: the completeness assertion at listing.rs:1744, and the notice judged from outside through the shipped binary. #461 untouched and unblocked; #455 correctly out of scope; docs/rust-rewrite-plan.md:172 correctly left as the archival record.

What I pressed on, and what I found

(a) The completeness claim: I could not construct the degradation from a configuration. I could construct it from a prior build.

From a configuration, the claim holds and I tried to break it. All five callers of WorkspaceCloneManager::new (migration.rs:1414, workspace_clone.rs:1395, lifecycle.rs:3383, lifecycle.rs:6468, launch.rs:3421) sit past their file's #[cfg(test)] line (432, 1341, 2911, 3242), so pub(crate) is doing no work that #[cfg(test)] is not already doing: in_cache is the only production constructor. Its two production reachers, dl/src/session.rs:173 and dl/src/commands.rs:381, both derive the root with xdg::clone_root_in from xdg::devlaunch_cache(), and nothing in production mutates XDG_CACHE_HOME mid-process. For every clone this build places, devlaunch:false / SIZE - / unsaved:null is unreachable.

It is not unreachable on the machine this migration is for. Seeding one devpod workspace whose localFolder is <retired repos_dir>/blooop/devlaunch/devlaunch-main-3j1t — exactly what a pre-#467 dl left behind on any machine that set the key — the release binary answers:

$ dl --ls --json
[ { "id": "devlaunch-main-3j1t", "devlaunch": false, "repo": null, "branch": null,
    "checkedOut": null, "path": null, "state": "Running", ..., "unsaved": null } ]

with SIZE reading - in the table. That is the triad verbatim, and it persists after the key is deleted from config.toml, at which point nothing prints the notice either.

There is a real consequence and not only a wording one: --prune's scan root moved inwards to <cache>/repos, so on those machines dl now reclaims nothing there, where before this PR --prune scanned that tree. #460 §3 accepted the stranding knowingly and refused the alternatives, so I am not asking for code. I am asking for the claim to be narrowed. listing.rs:451-462 says "A clone's directory is clone_root_in of the cache directory plus owner, repo and id, so it is inside cache_dir by construction", and carves out a moved XDG_CACHE_HOME but not a prior build's configured root; #467's breadcrumb says the case "has no way to be built". True of every clone this build places. Not true of the machine the notice is written for.

(b) The notice. The divergence from #460 is the right call, the reach is as claimed, nothing on disk is touched.

I agree with firing on presence rather than on difference. #460's version needs the value resolved and compared against a derived default, and §6.1 deletes the resolver in the same breath; a wrong comparison fails silently toward the one outcome principle 1 forbids. The cost is one true line to a user whose value happened to equal the default.

Reach verified live against the release binary with a devpod shim: --ls --json, --prune -y and --reconcile -y each print it exactly once; dl --ls's table is silent as designed. No double-print is possible from the background refresh child, because ProcessRunner::detach nulls stderr (devlaunch-runner/src/lib.rs:637); each command opens records at most once and cold.rs:43 memoises report().

Principle-1 line holds. The seeded tree at the retired root survived --ls, --prune -y, --reconcile -y and --purge -y intact. Nothing in the tree ever writes the configured value; it reaches render::retired_keys and stops.

(d) --purge is the one omission I would want closed or written down.

rust/dl/src/commands.rs:801purge_devlaunch_data goes through lifecycle::purge_plan and never session::open_records, so report() never runs. Verified: with the key set, --purge -y prints no notice. What the user sees instead is

Leaving 1 workspace(s) devlaunch did not create:
  - devlaunch-main-3j1t

an opaque id with no path (render.rs:1334 prints workspace.id alone), while the same run deletes <cache>/devlaunch including the metadata.json record that was the last thing pointing at that tree. The tree survives, and the notice returns on the next records-opening command while the key remains in config.toml, so this is not permanent silence. But --purge is the one command whose whole promise is "nothing of devlaunch's is left", and it is the run that will not mention the directory. Naming the sources in the leaving list is #461's; printing this notice is not — render::retired_keys needs only session::worktree_config(). #467's breadcrumb enumerates the commands that say it and omits purge rather than declining it, which is why I would rather see the decision recorded on #461 than left implicit.

(c) The refresh_cache regression is small, and the direction is right.

rust/dl/src/commands.rs:367. Verified with fetch_interval = "soon": dl --refresh and dl --completion-data now exit 0 and do their work, while dl --ls --json still exits 1 naming the file and the parse error. So a broken config is never silently in effect: the only live key left is fetch_interval, refresh_cache no longer reads it, and every path that would act on a config still refuses. What is lost is early diagnosis on three maintenance commands, which is a fair trade against a malformed config blocking completions from installing. Accepted. See Standards 2 for the sharper edge that survives.

(e) expand_tilde is dead, and the value is quoted verbatim.

No reference to config::expand_tilde, is_ours_to_create or ensure_repos_dir survives. The only expand_tilde left in the tree is an unrelated private one at flows/completion.rs:458 over rc-file paths, which pre-dates this PR and is a different signature. Nothing is lost: ~user was already left alone, and the config path was its only caller. Verbatim quoting confirmed live — repos_dir = "~/custom/repos" prints '~/custom/repos', unresolved.

Verdict

Approve. No blocking findings. (--approve is refused on a same-account PR, so this is posted as a comment and the written verdict is the gate.)

The deletion is the right shape, the invariant is genuinely enforced for everything this build places, and the hard principle-1 line holds under test: nothing at a configured repos_dir is moved, deleted, or written by any command I could reach.

Two things I would rather see land before merge, neither of which blocks:

  • --purge says nothing (commands.rs:801). Either print the notice there, or record on #461 that purge deliberately does not.
  • test/conftest.py:71-79 now understates a guard this same PR made load-bearing in the write direction.

And one wording change with no code behind it: narrow the completeness claim at listing.rs:451-462 to the clones this build places, since the degraded row is still reachable on the machine the notice is written for.

The review built the degradation this ticket closes. Not from a
configuration, which is now unable to express placement, but from a prior
build: a clone an earlier dl put under repos_dir still reads as someone
else's, and retiring the key moved the pruned root inwards, so nothing
reaches it. Deleting the key stops the notice too, and then nothing names
that tree at all.

#460 accepted that stranding knowingly. What is fixed is that no new such
clone can be made; what is not fixed is the ones already on disk. Say
that in both places rather than claiming the whole population, and
correct the count on the contract page, where a third thing was
reproduced under a heading that said two.
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.

Retire repos_dir

1 participant