Prune the package cache before updating (#6734)

The pacman cache grows without bound across updates, and nothing in the
update flow ever reclaimed it. On a machine that has been updating for a
while it reaches several gigabytes of superseded versions that nothing
will ever install again.

Prune it with paccache -rk2 as the first step of an update. Both halves
of that placement are load-bearing.

Keeping two versions rather than one preserves the rollback path. The
cache is Arch's only offline downgrade: when an update breaks a single
package, reinstalling its predecessor from here is the surgical fix,
where a snapshot rollback would revert every other package too. Pruning
before the packages update means the installed version is still the
newest cached, so it survives along with a spare. Retention is by
version order and never consults what is installed, so that holds while
the installed version is among the two newest cached; a deliberate
downgrade or repeated failed transactions can stack newer archives on
top of it.

Running before the snapshot is what actually frees the space. The cache
sits on the snapshotted root subvolume, so a prune taken afterwards
leaves the fresh snapshot holding those extents and reclaims nothing
until it ages out of the number cleanup.

A failed prune warns and continues. Cache housekeeping should not trip
the update's ERR trap and tell the user their update went wrong.

This runs after omarchy-update-requires-free-space, so it reclaims space
during healthy updates but does not rescue a machine already under the
10 GiB gate.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
David Heinemeier Hansson
2026-08-12 11:56:02 +02:00
committed by GitHub
co-authored by Claude Opus 5
parent cd84583b56
commit fd23ca023a
5 changed files with 81 additions and 0 deletions
+1
View File
@@ -68,6 +68,7 @@ for command in \
pkexec \
systemd-inhibit \
omarchy-update-dev \
omarchy-update-pkg-prune \
omarchy-update-keyring \
omarchy-update-system-pkgs \
omarchy-migrate \
+1
View File
@@ -35,6 +35,7 @@ for command in \
omarchy-toggle-idle \
pkexec \
systemd-inhibit \
omarchy-update-pkg-prune \
omarchy-update-dev \
omarchy-update-keyring \
omarchy-update-system-pkgs \
+63
View File
@@ -0,0 +1,63 @@
#!/bin/bash
set -euo pipefail
source "$(dirname "$0")/base-test.sh"
test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT
stub_bin="$test_tmp/bin"
mkdir -p "$stub_bin"
write_stub() {
local name="$1"
local body="$2"
cat >"$stub_bin/$name" <<SH
#!/bin/bash
$body
SH
chmod +x "$stub_bin/$name"
}
run_pkg_prune() {
PATH="$stub_bin:$PATH" "$ROOT/bin/omarchy-update-pkg-prune"
}
# Pin the keep count above one.
write_stub sudo 'printf "%s\n" "$*" >"$PACCACHE_LOG"; exit 0'
PACCACHE_LOG="$test_tmp/args" run_pkg_prune >"$test_tmp/prune.out" 2>&1
grep -q 'paccache' "$test_tmp/args" || fail "cache prune runs paccache"
grep -qE 'paccache .*-rk2' "$test_tmp/args" ||
fail "cache prune keeps more than one version" "$(cat "$test_tmp/args")"
pass "cache prune leaves a rollback version to spare"
# Housekeeping failure must not abort the update.
write_stub sudo 'exit 1'
run_pkg_prune >"$test_tmp/fail.out" 2>&1 ||
fail "cache prune survives paccache failure"
grep -q 'Could not prune the package cache' "$test_tmp/fail.out" ||
fail "cache prune warns when it fails" "$(cat "$test_tmp/fail.out")"
pass "cache prune warns but does not abort the update"
# Ordering is the whole guarantee: rollback before the packages update, space
# before the snapshot.
line_of() {
grep -n "^[[:space:]]*$1\b" "$ROOT/bin/omarchy-update" | head -1 | cut -d: -f1
}
prune_line=$(line_of omarchy-update-pkg-prune)
snapshot_line=$(line_of omarchy-snapshot)
pkgs_line=$(line_of omarchy-update-system-pkgs)
[[ -n $prune_line && -n $snapshot_line && -n $pkgs_line ]] ||
fail "omarchy-update runs the cache prune, the snapshot, and the packages update"
(( prune_line < pkgs_line )) ||
fail "cache prune runs before the packages update" "prune: $prune_line, packages: $pkgs_line"
pass "cache prune runs before the packages update"
(( prune_line < snapshot_line )) ||
fail "cache prune runs before the snapshot" "prune: $prune_line, snapshot: $snapshot_line"
pass "cache prune runs before the snapshot pins what it removes"