Skip to content

Mark installed kernel packages as automatically installed. - #1081

Open
AlexB7 wants to merge 1 commit into
linuxmint:masterfrom
AlexB7:autoremove-kernel-fix
Open

Mark installed kernel packages as automatically installed.#1081
AlexB7 wants to merge 1 commit into
linuxmint:masterfrom
AlexB7:autoremove-kernel-fix

Conversation

@AlexB7

@AlexB7 AlexB7 commented Aug 13, 2026

Copy link
Copy Markdown

Fixes: #938 and #1078

Summary

apt autoremove (and mintupdate's own "Remove obsolete kernels and dependencies" automation, which just shells out to apt-get autoremove --purge -y) was never cleaning up old kernels installed through mintupdate. The root cause: kernel packages installed via the GUI were being recorded by apt as manually installed instead of automatically installed, so they never became autoremove candidates in the first place and just accumulated indefinitely.

Root cause

mintupdate installs the real, version-specific kernel packages (linux-image-*, linux-headers-*, linux-modules-*, linux-modules-extra-*) directly by package name in two places:

  • aptUpdater.py's find_changes() fallback path (no meta package tracks the exact point-release version), which ends up in mintUpdate.py's install().
  • kernelwindow.py's install_kernels(), used by the kernel selection window, always installs the literal versioned packages.

Because these are requested by name rather than pulled in transitively as a dependency of a meta package (like a normal apt upgrade would do), apt/aptkit records them as manually installed. Manually installed packages are permanently exempt from apt autoremove, so every kernel mintupdate ever installs this way sticks around forever.

I confirmed this empirically on a test system: apt-mark showmanual listed every installed kernel package (image/headers/modules/ modules-extra, across every installed kernel version) as manual, and apt-mark showauto returned none.

aptkit already has a mechanism for this — package names can carry an #auto suffix (see aptworker._mark_packages_for_installation()) telling the worker to record the package as automatically installed (pkg.mark_install(False, False, from_user=False)) instead of manually. mintupdate never used it.

Fix

  • Classes.py: add is_real_kernel_package(), a small regex helper that identifies actual version-specific kernel packages (linux-image-6.17.0-41-generic, linux-headers-7.0.0-28, linux-modules-iwlwifi-6.8.0-41-generic, ...) as opposed to meta packages (linux-generic, linux-generic-hwe-24.04, linux-virtual, ...), which should stay manual, same as they would after a normal install. Rather than enumerating package flavors, it keys off the embedded VERSION-ABI kernel version (\d+\.\d+\.\d+-\d+), which versioned kernel packages always contain and meta package names never do (an HWE series suffix like -hwe-24.04 has no third version component or ABI number). This also covers versioned flavors mintupdate doesn't install itself but may upgrade in place on a same-ABI respin (linux-modules-iwlwifi/ipu6/usbio-*, linux-hwe-X.Y-headers-*), which would otherwise get flipped from automatic to manual by an explicit-by-name upgrade.
  • mintUpdate.py install(): tag real kernel package names with #auto before adding them to the list handed to aptkit.
  • kernelwindow.py install_kernels(): tag all to_install kernel package names with #auto (everything built there is already a real, versioned package, so no filtering needed).

History: when this broke

Kernel autoremoval used to work for most users, and the regression point is identifiable:

  • 2009 - Nov 2024 (synaptic era): every install path shelled out to synaptic, which uses standard libapt semantics - explicitly selected packages are marked manual, dependencies are marked auto. A normal kernel update in the main list only names the meta package, so the versioned kernel packages arrived as dependencies, were marked auto, and "apt autoremove" cleaned them up correctly.
  • Two paths never worked, even then: the kernel selection window (since the Feb 2016 revamp, 052c101) and the no-meta fallback in the update checker (Dec 2016, 753a01c; current KERNEL_PKG_NAMES form May 2018, 47ff47f) both feed explicit versioned package names to the installer, which marks them manual. This was largely masked because most users get kernels via the meta packages.
  • mintupdate 7.0.0 (Nov 2024, the aptkit migration - d54ac61, cfc17af) broke the main path as well: aptkit's simpleclient had just gained a python-apt dependency-flattening step (linuxmint/aptkit 18d255f, 2024-09-11) that converts resolver-discovered dependencies into explicitly-named requests, and the aptkit worker marks explicit requests manual. From 7.0.0 on, every kernel package mintupdate installs ends up manually installed, which matches this issue being reported against v7.0.x.

The "#auto" marker this PR uses has been supported by the aptkit worker the whole time - it was inherited from aptdaemon when aptkit was forked (April 2022) - it just was never used by any client.

Design note: kernel window installs are also tagged

Tagging the kernel selection window's installs #auto is a deliberate choice worth calling out: it means a kernel version the user explicitly picked there becomes autoremove-eligible once superseded. Apt's built-in protection always keeps the running kernel and the newest installed ones, and the "Remove obsolete kernels and dependencies" automation is opt-in and advertises exactly this behavior — but a user who installs an older kernel as a deliberate boot fallback, isn't running it, and has that automation enabled could see it cleaned up. If maintainers prefer to treat kernel-window installs as an explicit manual choice, dropping the kernelwindow.py hunk keeps the main-update-list fix (the #938 scenario) intact.

Companion fix

This alone isn't sufficient — aptkit.simpleclient.SimpleAPTClient .install_packages() locally resolves dependencies and flattens any newly-discovered packages into the request list without preserving the #auto tag, which silently drops the auto/manual distinction even for well-behaved meta-package-driven kernel installs. A companion fix is needed in aptkit itself: https://github.com/linuxmint/aptkit (PR linuxmint/aptkit#19).

There is no hard version lockstep, so the two can land independently: with an unpatched aptkit, the kernel window path (commit_changes) does no local resolution and works fully, and on the update-list path the tagged name still reaches the aptkit worker (which already understands #auto); only the local dependency-flattening step misses it, printing a harmless "Package ... not found in the cache!" line. Once the aptkit fix is released, mintupdate's aptkit dependency in debian/control should be bumped accordingly.

Testing

Manually tested on a Mint 24.04-based system:

  1. Built and installed the patched mintupdate + aptkit.
  2. Installed three kernel updates through the normal Update Manager list.
  3. Confirmed with apt-mark showauto that all installed image/headers/modules/modules-extra packages for those kernels are now flagged automatic (previously they showed up under apt-mark showmanual instead).
  4. Confirmed with apt-get autoremove --dry-run that the superseded kernel packages are now correctly identified as removal candidates once a newer kernel supersedes them, and that the currently running kernel is left alone.
  5. Ran apt autoremove for real and confirmed the superseded kernel packages were removed while the running kernel remained intact and bootable.

No existing tests cover the modified install-list-building code paths (tests/test_kernel_window.py doesn't touch install_kernels()'s to_install construction).

Claude Fable 5 was used to fix these issues.

mintupdate installs kernel packages (linux-image-*, linux-headers-*,
linux-modules-*) directly by name whenever no meta package tracks the
exact version, and always does so from the kernel selection window.
Since these are requested by name rather than pulled in as a
dependency, apt records them as manually installed, so "apt
autoremove" (and mintupdate's own "remove obsolete kernels"
automation, which just runs apt-get autoremove) can never reclaim
them once superseded, and old kernels accumulate indefinitely.

Tag these package names with aptkit's "#auto" suffix convention
before handing them to aptkit, so they get flagged the same way a
normal kernel upgrade via a meta package would. The new
is_real_kernel_package() helper distinguishes version-specific
kernel packages from meta packages (which must stay manual) by
looking for an embedded VERSION-ABI kernel version rather than
enumerating package flavors, so versioned flavors such as
linux-modules-iwlwifi-* and linux-hwe-X.Y-headers-* are covered too.

A companion fix in aptkit's SimpleAPTClient.install_packages() is
needed for the update-list path, where local dependency resolution
previously dropped the "#auto" marker; without it this change still
works, but dependency packages discovered client-side get recorded
as manual.

Fixes linuxmint#938.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

Automatic kernel removal does not appear to be working

1 participant