Skip to main content

max / alloy

Ask lsblk for the column it builds its tree around `filesystem_device` looks for the mapper device as a child of the LUKS container, and `partition_contents` asked lsblk for `PATH,FSTYPE`. lsblk nests its JSON around the NAME column and returns a flat array without it, so the container and the mapper inside it came back as siblings and the child was never there to find. Every encrypted install read as "an unopened LUKS container; the deploy left it closed", including the ones where it was open, which is all of them. So the encrypted path could not have completed even with a deploy that exited 0, and the message it failed with named the opposite of what was true. The two tests over this parser hand-write the nesting and passed the whole time, which is the shape of the mistake: the parser was right and the command feeding it was not. The new test pins the column list instead. Measured in a VM against a real failing install: the same lsblk with NAME added nests /dev/mapper/root under /dev/vda4, and without it lists them side by side.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-09 23:44 UTC
Signed with PGP, not checked
Commit: d778a6052f01694fc133db418cfcde194e34fcd7
Parent: 61c194c
1 file changed, +26 insertions, -1 deletion
@@ -591,9 +591,20 @@
591 591 const LUKS_FSTYPE: &str = "crypto_LUKS";
592 592
593 593 /// Ask lsblk what filesystem is on `partition`, and what it holds open.
594 + ///
595 + /// **`NAME` is in the column list and nothing reads it.** lsblk builds its tree
596 + /// around that column, and asking for `PATH,FSTYPE` alone returns a flat array
597 + /// with the container and the mapper device inside it as siblings, no
598 + /// `children` anywhere. [`filesystem_device`] looks for a child, so without
599 + /// this every encrypted install read as a LUKS container nobody had opened —
600 + /// including the ones where it was open, which is every one of them.
601 + ///
602 + /// Measured in a VM 2026-08-09 against the failing install: the same command
603 + /// with `NAME` added nests `/dev/mapper/root` under `/dev/vda4`, and without it
604 + /// lists them side by side.
594 605 fn partition_contents(partition: &str) -> Invocation {
595 606 Invocation::new("lsblk")
596 - .args(["-J", "-o", "PATH,FSTYPE"])
607 + .args(["-J", "-o", "NAME,PATH,FSTYPE"])
597 608 .arg(partition)
598 609 }
599 610
@@ -5725,6 +5736,20 @@
5725 5736 );
5726 5737 }
5727 5738
5739 + // The two tests above hand-write the nesting, so they passed for as long as
5740 + // the command never produced it. lsblk builds its tree around the NAME
5741 + // column and emits a flat array without it, which made every encrypted
5742 + // install read as a container nobody had opened. Pin the column list, since
5743 + // that is the part that was wrong.
5744 + #[test]
5745 + fn the_partition_listing_asks_for_the_column_lsblk_nests_around() {
5746 + let shown = Stage::Run(partition_contents("/dev/sda3")).display();
5747 + assert!(
5748 + shown.contains("NAME"),
5749 + "without NAME lsblk returns a flat list and no child is ever found: {shown}",
5750 + );
5751 + }
5752 +
5728 5753 // The branch this code cannot verify against a real disk from here. If bootc
5729 5754 // ever leaves the container shut, the install has to say so rather than
5730 5755 // mount the container and fail three stages later about a superblock.