From 04aafcb78134dd3a92c6af457b2d4b1402e49450 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 19 Jul 2026 20:58:39 -0700 Subject: [PATCH] Retry the partition commit when udev holds the disk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full-disk installs flaked with "Partition(s) 1, 2 on /dev/vda have been written, but we have been unable to inform the kernel of the change": archinstall's disk wipe and parted's own partition registrations fire udev probes, and when one is still holding the disk mid-commit, the BLKPG sync fails and the install halts. The table is written correctly when this happens — only the kernel's view is stale — so settle udev before partitioning and redo the filesystem operations (wipe, partition, format) when parted throws exactly this error. Recoveries are logged so they show up in the support log. Reproduced headlessly by booting the ISO on a serial console and driving the real installer: stock failed 1 in 5 full-disk cycles at this exact phase, with an audit watch confirming nothing else writes the disk. With the fix, 13 of 13 cycles got cleanly through partitioning. Co-Authored-By: Claude Fable 5 --- .../orchestrator/archinstall_adapter.py | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/configs/airootfs/usr/share/omarchy-iso/orchestrator/archinstall_adapter.py b/configs/airootfs/usr/share/omarchy-iso/orchestrator/archinstall_adapter.py index f7c6c2d..43f71b0 100644 --- a/configs/airootfs/usr/share/omarchy-iso/orchestrator/archinstall_adapter.py +++ b/configs/airootfs/usr/share/omarchy-iso/orchestrator/archinstall_adapter.py @@ -3,7 +3,7 @@ ONLY this module imports from archinstall. Everything else uses these helpers. If archinstall's API churns, the blast radius is contained here. -Tested against archinstall 4.3 (Python 3.14). +Tested against archinstall 4.4 (Python 3.14). The canonical call sequence (mirrored from archinstall.scripts.guided.py) is: @@ -44,7 +44,7 @@ from typing import Iterator from archinstall.lib.args import ArchConfig, ArchConfigHandler from archinstall.lib.authentication.authentication_handler import AuthenticationHandler from archinstall.lib.disk.filesystem import FilesystemHandler -from archinstall.lib.disk.utils import get_parent_device_path, get_unique_path_for_device +from archinstall.lib.disk.utils import get_parent_device_path, get_unique_path_for_device, udev_sync from archinstall.lib.hardware import SysInfo from archinstall.lib.installer import Installer from archinstall.lib.mirror.mirror_handler import MirrorListHandler @@ -52,6 +52,8 @@ from archinstall.lib.models import Bootloader from archinstall.lib.models.device import DiskLayoutType, EncryptionType from archinstall.lib.models.users import User +from .ui import info + def load_arch_config(config_path: Path, creds_path: Path) -> ArchConfigHandler: """Build an ArchConfigHandler from on-disk JSON. @@ -83,10 +85,30 @@ def make_mirror_handler(offline: bool = True) -> MirrorListHandler: def perform_filesystem_operations(arch_config: ArchConfig) -> None: """Partition, format, encrypt. archinstall's FilesystemHandler is its own object (separate from Installer) so we run it before opening the - Installer context manager.""" + Installer context manager. + + parted's partition-table commit races udev: the disk wipe and the first + BLKPG partition registration trigger probes that briefly hold the disk + open, and the kernel then refuses to register the remaining partitions + (_ped.IOException: "Partition(s) ... have been written, but we have been + unable to inform the kernel"). The table is written correctly when that + happens — only the kernel's view is stale — so settle udev and redo the + operations. The wipe/partition/format sequence is idempotent.""" if not arch_config.disk_config: raise RuntimeError("disk_config missing from arch config") - FilesystemHandler(arch_config.disk_config).perform_filesystem_operations() + + handler = FilesystemHandler(arch_config.disk_config) + + attempts = 3 + for attempt in range(1, attempts + 1): + udev_sync() + try: + handler.perform_filesystem_operations() + return + except Exception as exc: + if attempt == attempts or "unable to inform the kernel" not in str(exc): + raise + info(f"› partition commit lost a udev race (attempt {attempt}/{attempts}); retrying") @contextmanager