holo-chroot/steamos-chroot: unguarded rmdir in the EXIT trap discards the chrooted command's exit status
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 72/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- bash, linux, shell
- Domain
- cli, operating-systems
Research direction
The fix lives in the cleanup_chroot_at() function in /usr/bin/holo-chroot, around line 246, where the final rmdir $dir at line 281 is unguarded while every other command is || :. Reproduce with a busy mountpoint held open under the chroot's /efi while running the chrooted command, then confirm the exit status is 1 despite success. Done when the chrooted command's exit status is preserved in that case, and an optional warning is printed when the directory is left behind.
Written by the indexing model from the issue text.
Description
Your system information
- Steam client version: n/a — this is a defect in a shell script shipped in
steamos-customizations-jupiter, not in the client - SteamOS version: reproduced by inspection against
steamos-customizations-jupiter 20260916.1-1, the newest build injupiter-mainat time of writing. Observed in the wild on asteamdeck-oobe-repairrecovery image (linux-neptune-616, e2fsprogs 1.47.3) - Opted into Steam client beta?: No
- Opted into SteamOS beta?: No
- Have you checked for updates in Settings > System?: Yes — the affected line is unchanged in the latest package
Please describe your issue in as much detail as possible
holo-chroot (steamos-chroot) can exit 1 after the chrooted command has fully succeeded, because the final rmdir in its EXIT trap is unguarded.
/usr/bin/holo-chroot runs under set -euo pipefail (line 17) and does its teardown from an EXIT trap:
284 CHROOTDIR=$(mktemp -d)
285 trap "cleanup_chroot_at $CHROOTDIR" EXIT
286
287 prepare_chroot_at $CHROOTDIR
288 chroot $CHROOTDIR "$@"
Inside cleanup_chroot_at() (line 246) every single command is guarded with || : — all eleven umounts, the flocks, and even the other rmdir at line 263:
263 rmdir "$DEV_DIR" || :
...
270 umount $dir/var || :
271 umount $dir/esp || :
272 umount $dir/efi || :
273 umount $dir/tmp || :
274 umount $dir/sys/firmware/efi/efivars || :
275 umount -R $dir/sys || :
276 umount $dir/run || :
277 umount -R $dir/proc || :
278 umount $dir/dev || :
279 umount $dir || :
280
281 rmdir $dir # <-- the only unguarded command in the function
So if any of those mountpoints is busy at teardown time, the umounts are tolerated as intended, but $dir is then necessarily non-empty, rmdir fails, and — because the script runs under set -e — errexit fires from inside the EXIT trap. The shell exits with rmdir's status and the exit status of chroot $CHROOTDIR "$@" is discarded.
The consequence is that any caller which checks steamos-chroot's exit status sees a spurious failure for work that actually completed. Here is a real instance, where update-grub demonstrably succeeded and steamos-chroot still exited 1:
+ steamos-chroot --no-overlay --disk /dev/sdb --partset A -- update-grub
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-linux-neptune-616
Found initrd image: /boot/amd-ucode.img /boot/initramfs-linux-neptune-616.img
Adding boot menu entry for UEFI Firmware Settings ...
done
Installing grub configuration file at /efi/EFI/steamos/grub.cfg
umount: /tmp/tmp.h5fd9aSs2I/efi: target is busy.
umount: /tmp/tmp.h5fd9aSs2I: target is busy.
rmdir: failed to remove '/tmp/tmp.h5fd9aSs2I': Device or resource busy
update-grub wrote its config and reported done; the only failures are in the teardown. An installer calling this in a sequence of steps aborts here, mid-install.
That set -e-inside-a-trap behaviour is easy to miss, so to be explicit about the mechanism:
$ bash -c 'set -e; cleanup() { rmdir /nonexistent; echo AFTER; }; trap cleanup EXIT; true'; echo $?
rmdir: failed to remove '/nonexistent': No such file or directory
1
AFTER never prints — errexit aborts from within the trap — and the script's status becomes 1 despite the body having succeeded. Removing set -e, or adding || :, both yield 0.
Steps for reproducing this issue
- Arrange for one of the chroot's mountpoints to be busy at teardown. Any of them will do; the simplest synthetic version is to hold a reference open while the chroot exits, e.g. run
steamos-chroot --no-overlay --disk $DISK --partset A -- update-grubwhile another process has a file open under the chroot's/efi. - Observe that the chrooted command completes and prints its normal success output.
- Observe that
steamos-chrootnonetheless exits1, with the only errors beingumount: ... target is busyfollowed byrmdir: failed to remove ...: Device or resource busy.
Suggested fix
Guard it like everything else in the function, which also keeps it consistent with line 263:
- rmdir $dir
+ rmdir $dir || :
Optionally warn so the leftover directory isn't silently abandoned:
rmdir $dir || echo >&2 "warning: left $dir behind; something is still using it"
Either way the exit status of the chrooted command is preserved, which is what callers rely on.
Note that why a mountpoint is busy is a separate question, and arguably a legitimate thing for a caller to have to deal with. This report is only about the teardown clobbering an otherwise successful exit status — a busy mountpoint shouldn't turn a completed operation into a failed one.
Found via a third-party SteamOS installer that calls steamos-chroot in sequence and checks each step: louij2/steamos_custom_install#12. Happy to supply more detail if useful.
- Dominant language
- No language data
- Stars
- 2.6k
- Forks
- 85
- PR merge metrics
- No merged PRs in 30d
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from ValveSoftware/SteamOS
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
ValveSoftware/SteamOS#2875 · 1 comment ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
ValveSoftware/SteamOS#2860 · 1 comment · 2 reactions ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
ValveSoftware/SteamOS#2856 ·
-
steam-short-session-tracker: incorrect registry backup path resets settings during automatic repairOpen
Difficulty 2/5 1-3 hours Newbie friendliness 80/100
ValveSoftware/SteamOS#2829 ·
-
Difficulty 1/5 Under an hour Newbie friendliness 68/100
ValveSoftware/SteamOS#1743 · 1 comment · 2 reactions ·
All issues in ValveSoftware/SteamOS
Similar issues
-
black --check exits 0 and prints All done when a multi file run is interruptedPossibly taken @jiteshgulati claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 2 days
-
README for cliPossibly taken @Janvi-kapoor claimed this today. OpenOSCI'26
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
GauravKarakoti/SecureFlow#1308 · 1 comment ·
Maintainers usually reply within 1 day
-
bug discussion
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
medialab/xan#1201 · 2 comments ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
ajeetdsouza/zoxide#1323 ·
Maintainers usually reply within 1 day
-
enhancement good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
graygarbage/HermesKey#21 ·