Skip to content
Browse documentation

Managed-link cleanup

Remove only broken or retired links whose ownership Etch can prove.

EtchReference

Etch automatically manages the lifecycle of links it created. Its ignored .etch/links/ receipts record ownership so old destinations can be retired without maintaining a growing list of obsolete paths.

Automatic retirement and housekeeping

When a selected, active module removes a link declaration or changes its destination from A to B, plan previews retirement of A. After a successful apply, Etch removes A only if it still matches its recorded link identity, then deletes the receipt. B is reconciled normally before retirement runs. A destination still declared by another module is preserved.

A full run without module arguments or --profile can also retire links from module directories actually removed from the repository. Partial/profile runs preserve those modules’ records. An existing module directory without its configuration is treated as unavailable, not as a proven removal. Git sparse exclusions and uninitialized submodules are also preserved; unavailable or failed Git checks provide no authority to retire a missing module.

False or deferred module conditions, incomplete catalogs, failed planning, and failed/partial applies do not authorize retirement. User-replaced links and ordinary files are preserved. Removing a declaration is distinct from merely omitting a module from a profile or temporarily disabling an action condition.

Housekeeping runs automatically; no clean action is required for it. It prunes receipts for missing/replaced links, invalid receipt JSON, and missing downloaded files. It preserves useful ownership records. It does not recursively scan user directories, contact the network or hash downloaded contents to find orphans. Planning only reports the work. Apply rechecks configuration, destinations and receipt identity before deletion; refreshed receipts are preserved. If eligibility only becomes known after a fact refresh, cleanup can wait for the next run.

Even a checkout with no remaining modules can run plan/apply for housekeeping when .etch/ already exists. This does not turn an arbitrary empty directory into a valid configuration repository.

Explicit cleanup actions

User-defined clean actions remain supported alongside automatic housekeeping. They remove only receipt-backed links that are broken or explicitly retired, subject to the active destination claim checks described below. They do not infer ownership merely because a link points into the repository.

{"clean": ["~", "~/.config"]}
{"clean": {"paths": ["~/.config"], "obsolete": ["~/.config/old-tool"]}}

Paths select directories; scanning is limited to their immediate children. Nested directories are not traversed, and symlinks used as scan roots are rejected. Explicit obsolete destinations must be immediate children of a selected root. Missing roots are harmless. No provider defaults are currently supported.

Ownership evidence

After creating or explicitly relinking a symlink, link writes a schema-versioned receipt beneath the consuming repository’s .etch/links/. The receipt records its absolute destination, literal target, device/inode identity, change timestamp and originating module. A preexisting already-correct link is not adopted. Receipts are local operational state in the consumer checkout, excluded from Git; they are not transferred as ownership proof between machines. Keep .etch/ in the consumer’s .gitignore (the template already does). Deleting it discards cleanup proof; it is not a cache that Etch can safely rebuild by adopting existing files.

Clean requires the current symlink to match that receipt exactly. Missing, malformed, unreadable or stale receipts provide no permission to delete. Symlinked receipt directories/files are not trusted. Changed or replaced links lose their ownership proof. Legacy links created before receipts were introduced remain unowned.

For an explicit clean action, broken targets qualify automatically once ownership is proven. Valid links qualify when explicitly listed in obsolete. This avoids removing another module’s configuration merely because the current run selects a partial profile. Automatic declaration retirement is handled separately by the lifecycle described above; explicit obsolete entries remain useful for deliberate cleanup.

Planning and execution

Plans enumerate removal destinations and claim each exclusively. Active link claims, including SKIP plans, therefore conflict with a clean removal of the same destination. Compatible shared parent-directory claims remain valid. Such conflicts are errors; the planner does not silently choose clean over another provider.

Inspection does not delete links or write receipts. Application rechecks receipt identity for all candidates before the first removal and again before each unlink. A restored target cancels an automatic broken-link removal; explicit retirement still applies. Successful removal also removes the matching receipt; if the link disappeared after planning, cleanup can remove only its stale receipt. Reapplication after removal is a no-op. Explicit actions retain normal module ordering and failure behavior; automatic retirement waits until apply succeeds.

This is conservative provenance, not tamper-proof authorization. Plugin code and the repository’s local state are trusted. Checks are not atomic against concurrent external filesystem changes; I/O errors can leave a partially applied action. Likewise, a failure to write a receipt after link creation leaves a link without cleanup proof and reports the error. Clean preserves anything it cannot prove owned.