Repository navigation
fix(updater): prune build workspaces no longer referenced by state - #1619
Merged
ilysenko merged 2 commits intoOct 24, 2026
Merged
Conversation
Each update left a ~2 GB workspaces/<version> directory behind, so the cache grew without bound (59 GB observed). prune() now also removes workspaces that neither the active build, the rebuilt package, the rollback package, nor an in-flight install transaction points into, leaving the installed build, its rollback, and any build in progress. A symlinked workspace root is never followed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ilysenko
approved these changes
Oct 24, 2026
ilysenko
left a comment
Owner
There was a problem hiding this comment.
Thanks for tracking this down — the retention set matches how the builder and rollback paths populate state, both call sites already run under check.lock, and the symlinked-root guard plus tests look good. Approving.
The earlier red checks came from the upstream 26.1021.22027 drift, now repaired on main (#1621); after updating the branch, CI is fully green.
Non-blocking follow-ups, fine for a later PR:
prune_workspacesscanscache_dir/workspaces, whilebuilder::build_updatewrites underconfig.workspace_root/workspaces. They are equal by default, but passing the configured workspace root would keep cleanup correct if it is overridden.prune_workspaces(...)?runs before the.debpruning, so one undeletable workspace now aborts the package cleanup that used to succeed. Consider best-effort per-entry removal or pruning packages first.- The new tests use
std::env::temp_dir()with manual cleanup; the crate already depends ontempfile, which would avoid leftovers when an assertion fails.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The updater leaks one full build workspace per upstream release.
cache_cleanup::prune()only removes superseded upstream.debfiles frompackages/; it never touchesworkspaces/<version>/, andbuilder::build_update()only wipes the workspace of the version it is about to rebuild. Each workspace holds the extracted app (codex-app/, ~1.7 GB) plus the rebuilt package indist/(~0.5 GB), so the cache grows by roughly 2 GB for every release and never shrinks.On one Fedora 44 install that had been auto-updating since August,
~/.cache/codex-update-managerreached 34 workspaces / 59 GB, while only 2–3 of them were still referenced bystate.json.This also contradicts
docs/updater.md, which already states that cleanup "retains the installed source, current candidate, and immediately previous rollback artifact" — that was true forpackages/, not forworkspaces/.Change
prune()now also calls a newprune_workspaces()that deletes aworkspaces/<entry>directory only when no persisted path points into it. Retained references:artifact_paths.workspace_dir(the active / most recent build)artifact_paths.package_path(rebuilt candidate, or the installed package)artifact_paths.rollback_package_path(immediately previous artifact)install_transaction.package_path(an in-flight install)Each reference is compared both as stored and canonicalized, so a missing file never causes its workspace to be treated as unreferenced by accident through path normalization. A symlinked
workspaces/root is never followed, and only real directories (not symlinks) inside it are considered.No new call sites:
prune()is already invoked after a successful upstream check and after a successful install, and both call sites run under thecheck.lockmutation lock introduced in #1141, so cleanup cannot race a build,install-ready,check-now, orrollbackin another process.Resulting behavior
Steady state is two workspaces (~4 GB), three only while a new build is in progress, instead of unbounded growth. Rollback keeps working exactly as before, since the rollback package's workspace is always retained (and
rollback::preserve_before_workspace_cleanupstill copies it out before a same-version rebuild).User-visible effect
Existing installs reclaim the leaked space automatically on the first check after upgrading. No config, state schema, or CLI changes; no new dependencies.
Scope
updater/src/cache_cleanup.rs,docs/updater.md,CHANGELOG.md.Validation
cargo fmt --all -- --checkcargo clippy -p codex-update-manager --all-targets -- -D warningsenv -u XDG_CONFIG_HOME cargo test -p codex-update-manager -- --test-threads=1— 125 passed, including 4 new tests:keeps_current_and_rollback_workspaces_only— of 4 workspaces, only the active and rollback ones survivekeeps_active_build_and_install_transaction_workspaces— a workspace still being built (nodist/yet) and one referenced only by an in-flight install transaction are keptremoves_workspaces_nothing_references— a dangling rollback path does not protect unrelated workspacesignores_symlinked_workspace_root— a symlinkedworkspaces/root is left alone and its target untouchedgit diff --checkstate.jsonreferenced, and confirmed the same retention set the new code computes; the subsequent 26.1021.22027 update built and installed successfully.Not tested: a full end-to-end run of the patched daemon across two consecutive upstream releases (that requires waiting for the next signed release).
Checklist