Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

holo-chroot/steamos-chroot: unguarded rmdir in the EXIT trap discards the chrooted command's exit status

Open Beginner friendly
#2,927 0 comments 0 reactions 0 assignees View on GitHub

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

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 in jupiter-main at time of writing. Observed in the wild on a steamdeck-oobe-repair recovery 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
  1. 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-grub while another process has a file open under the chroot's /efi.
  2. Observe that the chrooted command completes and prints its normal success output.
  3. Observe that steamos-chroot nonetheless exits 1, with the only errors being umount: ... target is busy followed by rmdir: 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from ValveSoftware/SteamOS

All issues in ValveSoftware/SteamOS

Similar issues

More CLI issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.