Skip to content

mintupdate shows phantom "Obsolete" kernels after interrupted removal (Linux Mint 21.3 Xfce) #1064

@xtpx

Description

@xtpx

Environment

  • Distribution: Linux Mint 21.3 (Virginia)
  • Edition: Xfce (64-bit)
  • mintupdate version: 5.9.7 (verified with mintupdate --version)
  • Kernel series: 5.15 LTS

Bug Description

When the user attempts to remove old kernels via the Update Manager's graphical interface (View → Linux Kernels) and the removal process is interrupted (e.g., by accidentally clicking "Refresh" while the operation is in progress), the interface continues to display many kernels as "Obsolete" even though they are no longer physically installed on the system. This incorrect state persists across reboots and kernel list refreshes.

Examples of affected kernel versions:
5.15.0-173, 5.15.0-171, 5.15.0-170, 5.15.0-168, 5.15.0-164, 5.15.0-163, 5.15.0-161, 5.15.0-160, 5.15.0-157, and others.

Steps to Reproduce

  1. Open Update Manager (mintupdate).
  2. Navigate to View → Linux Kernels.
  3. In the list of installed kernels, select an older one (not the currently active kernel) and click Remove.
  4. While the removal process is running, click the Refresh button (or trigger another update/refresh).
  5. The removal is interrupted (partial removal or error).
  6. After the interruption, the Linux Kernels window still shows the removed kernel(s) as "Obsolete", and often also shows other previously removed kernels.

Actual Behavior

  • The GUI lists many kernels with the "Obsolete" status, falsely implying that they are still physically installed.
  • The list does not reflect the true state of the system (confirmed via dpkg and filesystem checks below).

Expected Behavior

  • After a kernel is successfully removed, it should disappear from the list (or at most appear as an "available for installation" candidate without the "Obsolete" label).
  • The kernel list should always match the output of dpkg --list | grep linux-image – only genuinely installed kernels should be shown as "Installed" or "Active".

Supporting Evidence

1. System tools confirm kernels are absent

$ dpkg --list | grep linux-image
ii  linux-image-5.15.0-176-generic  5.15.0-176.186  amd64  Signed kernel image generic
ii  linux-image-5.15.0-177-generic  5.15.0-177.188  amd64  Signed kernel image generic

Only two kernels are actually installed. The phantom kernels are not present in the package manager.

2. Direct python3-apt check

import apt
cache = apt.Cache()
pkg = cache.get('linux-image-5.15.0-171-generic')
print(pkg.is_installed)   # -> False

The apt cache correctly reports that the phantom kernel is not installed.

3. Filesystem check

No leftover files from old kernels exist in /boot or /lib/modules:

/boot/vmlinuz-* and /boot/initrd.img-* → only 5.15.0-176 and 5.15.0-177
/lib/modules → only 5.15.0-176-generic and 5.15.0-177-generic

4. The data-generation script (checkKernels.py) produces correct output

When executed manually, it correctly marks phantom kernels as installed=0:

$ /usr/lib/linuxmint/mintUpdate/checkKernels.py | grep "5.15.0-171"
KERNEL###005.015.000.171.181###5.15.0-171###5.15.0-171.181###0###0###1###1###jammy-updates###60###-generic

This proves that the source data fed to the GUI is accurate – the kernel is correctly reported as not installed (installed=0).

5. Extensive troubleshooting (none effective)

All of the following steps were performed, yet the phantom entries remained:

  • sudo apt clean, autoremove, autoclean, dpkg --configure -a
  • Removal of caches: ~/.cache/mintupdate, ~/.config/mintupdate, /var/cache/mintupdate, /var/lib/mintupdate
  • Resetting dconf keys for mintupdate
  • Complete purge and reinstall of the mintupdate package
  • Deletion and re-download of apt lists (/var/lib/apt/lists/*)
  • Reboots after each step

6. Integrity of the patched file

The following command shows only the lines that were added or modified by the fix:

$ grep -n -E "import subprocess|truly_installed|prefix.*linux-image|ver = pkg\[len|full_ver not in truly" /usr/lib/linuxmint/mintUpdate/kernelwindow.py
6:import subprocess
515:            truly_installed = set()
516:            prefix = "linux-image-"
521:                    if pkg.startswith(prefix) and pkg.endswith(suffix) and pkg != "linux-image-generic":
522:                        ver = pkg[len(prefix):-len(suffix)] if len(suffix) > 0 else pkg[len(prefix):]
523:                        truly_installed.add(ver + suffix)
525:            truly_installed = None  # fallback
551:            if truly_installed is not None:
553:                if full_ver not in truly_installed:

Root Cause Analysis

The issue lies in the GUI component, most likely in /usr/lib/linuxmint/mintUpdate/kernelwindow.py, specifically in the build_kernels_list method. Although the data source correctly supplies installed=0 for removed kernels, the GUI appears to override or ignore this flag, relying instead on its own internal cache or previously stored state. As a result, kernels that were once installed continue to be treated as "installed" and are displayed under the "Obsolete" category.

Suspicious area (lines ~560–570 in kernelwindow.py):

if installed:
    self.installed_kernels.append((kernel_type, version))
    if not used:
        Gdk.threads_enter()
        self.button_massremove.set_sensitive(True)
        Gdk.threads_leave()
        self.remove_kernels_listbox.append(MarkKernelRow(Kernel(version, kernel_type, origin, installed),
                                                            self.marked_kernels, version_id,
                                                            newest_supported_in_series))

The variable installed here may remain True for previously removed kernels because of incomplete reset of internal state after an interrupted operation.

Proposed Fix (tested on 21.3 Xfce, works with kernel updates and removal through GUI)

A direct dpkg verification is added right before the list of installed kernels is built. This ensures that even if internal state is stale, kernels not truly installed are never displayed. The fix uses the standard CONFIGURED_KERNEL_TYPE variable and string slicing for robustness, making it compatible with all kernel types.

Changes to /usr/lib/linuxmint/mintUpdate/kernelwindow.py:

  1. Add import subprocess at the top of the file (if not already present).
  2. In build_kernels_list, after self.installed_kernels = [], insert a block that builds a set truly_installed from dpkg --list.
  3. In the block that checks if installed:, add a condition: if truly_installed is available and the current kernel version is not in it, force installed = False.

This minimal patch (only a few lines added) has been tested and stabilises the kernel list display. It preserves all original functionality, including the kernel removal button logic.

Diff for kernelwindow.py (Proposed Fix):

--- kernelwindow.py.orig
+++ kernelwindow.py
@@ ... @@
 import re
+import subprocess
 ...
 class KernelWindow(Gtk.Window):
     ...
     def build_kernels_list(self, kernels):
         ...
         self.installed_kernels = []
+        try:
+            dpkg_out = subprocess.check_output(["dpkg", "--list"], universal_newlines=True)
+            truly_installed = set()
+            prefix = "linux-image-"
+            suffix = CONFIGURED_KERNEL_TYPE
+            for l in dpkg_out.splitlines():
+                if l.startswith("ii") and "linux-image-" in l and "unsigned" not in l:
+                    pkg = l.split()[1]
+                    if pkg.startswith(prefix) and pkg.endswith(suffix) and pkg != "linux-image-generic":
+                        ver = pkg[len(prefix):-len(suffix)] if len(suffix) > 0 else pkg[len(prefix):]
+                        truly_installed.add(ver + suffix)
+        except Exception:
+            truly_installed = None
         ...
         for kernel in kernel_list_prelim:
             ...
             if installed:
+                if truly_installed is not None:
+                    full_ver = version + kernel_type
+                    if full_ver not in truly_installed:
+                        installed = False
                 self.installed_kernels.append((kernel_type, version))
                 ...

This fix has been successfully applied on the same system and completely eliminates the phantom kernel entries, with no side effects.

Additional Notes

  • The bug is reproducible on a fresh Linux Mint 21.3 Xfce installation after deliberately interrupting kernel removal.
  • The proposed fix has been tested for stability through multiple reboots and a kernel update (5.15.0-179). It does not affect the normal operation of the Update Manager, including the kernel removal button (which correctly becomes active when spare kernels are present).
  • A final test with an actual kernel removal through the GUI (5.15.0-176) was completed successfully. The removal left the system with only the active and one spare kernel, and the interface correctly switched the "Remove Kernels" button to inactive state, proving that the fix fully respects the original protection logic.
  • A temporary rollback to the original kernelwindow.py from a 2024 snapshot was performed. SHA256 hashes confirmed the file was identical. The "generic" tab behaviour remained unchanged, confirming the fix does not alter the kernel type selection UI – it is governed by standard mintupdate logic (the allow-kernel-type-selection setting and the absence of other kernel flavours).
  • A simulation using the original checkKernels.py output from a 2024 snapshot was conducted. The patched filter correctly identified a kernel marked as installed=1 that was not present in the current dpkg list, confirming the fix reliably eliminates phantom entries inherited from prior interrupted removals.
  • This behaviour was not observed in older Mint versions (e.g., 20.x), suggesting it is associated with the newer kernel window design and the checkKernels.py helper.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions