Three changes from kuhy's follow-up: - An explicit standing instruction that when vmbox breaks the next session FIXES it -- not works around it, not reports it as a limitation, not quietly falls back to testing on the host. Bugs found by real use are the return on having built this. - Guidance to attach to an existing sandbox or create one as appropriate, rather than tearing down and rebuilding per command (`vm reset` is ~2s). - A build-speed job with measurements rather than guesses: the last build pulled 197 packages / 396 MiB over the internet and spent 45s waiting on cloud-init, while the host's 114 GB pacman cache already holds every one of the packages checked. Exposing that cache read-only is the obvious first move; trimming base-devel and shortening the cloud-init wait follow. The smoke gate stays regardless -- a faster build that fails open would be a bad trade. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdyMBGCrGVGDWtJ9xe48f3
8.5 KiB
Next session: make vmbox the default place scripts get tested
Paste everything below into a fresh Claude session.
~/utils/vmbox is a working disposable-Arch-VM sandbox (built and verified
2026-08-22, pushed to github.com/kuhyx/utils). Read ~/utils/vmbox/README.md
first — it documents the design and the traps.
Three jobs this session, in order.
Job 1: harden it by using it for real
vmbox has only ever been driven by one session, against a handful of scripts.
testsAndMisc/linux_configuration is ~1090 files, ~170 of which need root.
Expect it to break. That is the point of this job: find the breakages by
running real work through it, and fix them.
Standing instruction for the whole session: when vmbox breaks, FIX IT. Do not work around it, do not report it as a limitation and move on, do not switch to testing on the host instead. A bug found here is the whole return on building the thing. Fix, commit, push, carry on with the list.
Reuse a sandbox when one is already up and suitable (vm list, then
vm run <name> ... — it starts the VM if stopped); create a fresh one when
you need a clean system or a different pinned clock. Do not tear down and
rebuild a sandbox for every command: vm reset is ~2s and is usually what you
actually want.
Work through this list, fixing vmbox whenever it gets in the way:
- The 8 Arch-only tests the repo's CI already names in
.github/workflows/shell-tests.yml(arch-testsjob) —test_hosts_*,test_makepkg_*,test_pacman_wrapper_security.sh,test_security_hardening.sh,test_shutdown_timer_monitor.sh. These are the ones its comments say "cannot run on an Ubuntu runner". Run them in a sandbox and report which pass. test_security_hardening.shspecifically: on a bare guest it reports its host checks as skipped. Install the real system first (install_core_system.sh), then re-run it, so those checks become real.setup_night_lockdown.sh— writes/etc/systemd/systemunits and an i2c module config. The i2c RGB part is hardware and will not work; confirm everything else does.boot_recovery/install.sh— pacman hooks + mkinitcpio. Highest blast radius of anything in the repo, and the best argument for the sandbox.- The hosts guard (
periodic_background/hosts/install.sh) — rewrites/etc/hostsand makes it immutable withchattr +i. Verify the immutable flag really lands in the guest, then thatvm resetclears it.
For each: vm share what it needs, clone into the guest, run it, report what
happened. When vmbox itself is the thing that broke, fix vmbox and commit.
Known-good invocation shape
vm share ~/testsAndMisc # bind mount, NOT a symlink (9p won't follow one)
vm share ~/utils # guard-lib lives here; several scripts need guardctl
vm new t1 --rtc 2026-08-22T20:55:00 # pin the clock: shutdown scripts gate on 21:00-05:00
vm run t1 'git clone --no-hardlinks -q /mnt/hostrepo/testsAndMisc ~/tam'
vm run t1 'echo y | sudo bash ~/tam/path/to/script.sh enable' # many prompt; stdin is closed
vm reset t1 # ~2s back to pristine
Traps already paid for — do not rediscover these
- A guest that boots but refuses ssh is almost never a network problem.
Run
systemctl list-jobson the guest viavm sshor the serial console. A unit that is enabled, has satisfied dependencies, and neither starts nor fails is QUEUED behind something. That cost most of a session. - Never grep a
bash -xtrace for the failure — you find where your pattern matched, not where execution stopped. Read the tail. pkill -f <pattern>kills the Bash-tool shell when the pattern appears in the command running it (exit 144). Usepgrep -f | xargs -r kill.- Full list:
~/.claude/projects/-home-kuhy/memory/reference-vmbox-guest-image-traps.mdandreference-qemu-shutdown-verification.md.
Rebuilding the image
vm build --force takes ~10 min. It ends with a smoke test that boots a real
sandbox and refuses to ship an unreachable image. Do not debug the image by
rebuilding per hypothesis — boot an overlay off the current base, fix it live
over the serial console (root autologin is on ttyS0 and ttyS1), confirm the
fix, and only then bake it into guest/provision.sh and rebuild once.
Job 2: make it the default, once Job 1 says it is trustworthy
kuhy's ask: "claude should ALWAYS defer to using this sandbox when testing stuff, ESPECIALLY when testing scripts."
Do Job 2 only after Job 1. A rule that routes work into a tool that breaks is worse than no rule. If Job 1 leaves vmbox flaky, say so and stop.
The rule belongs in ~/.claude/CLAUDE.md. Points to settle with kuhy before
writing it — these are real forks, not rhetorical:
- What triggers it. "Any script" is too broad: it would send a one-line
python_pkgunit test through a VM boot. A sharper line is anything that needs root, writes outside$HOME, touches systemd/pacman//etc, or could power the machine off. - What it replaces. Today's
CLAUDE.mddevelopment workflow says "run the actual script to verify it does what it is supposed to do". Does the sandbox become the first run, or an extra step before the host run? Note the standingfeedback-verify-real-deployment-pathmemory: testing somewhere that is not the real deploy target has burned kuhy before, so a sandbox pass must NOT be presented as proof the host is fine. - The escape hatch. Anything hardware-bound (adb/phone, GPU, i2c, the Huion tablet) cannot run in a VM. The rule needs a stated exception, or it will be violated on the first phone task and quietly stop being followed.
- Where it lives.
CLAUDE.mdis always-on context, so keep it to a few lines and link out. Pertoken-spend.instructions.md, a long rule there is a permanent per-turn tax.
Draft it, get kuhy's sign-off on those four points, then write it.
Job 3: make vm build faster than ~10 minutes
Measured on the last real build, so this is a known target rather than a guess:
- 45s waiting for cloud-init on the guest's first boot.
- 197 packages, 396 MiB downloaded over the internet (1273 MiB installed).
- The host's own pacman cache is 114 GB and already contains every one of
xorg-server i3-wm base-devel git strace jq kcov bats rsync xterm dmenu shellcheck— verified. The build is re-downloading packages that are already on this machine.
Ideas, cheapest first — measure before and after, do not assume:
- Expose the host pacman cache to the build VM read-only (
-virtfsthe wayvm sharealready does) and point the guest's pacman at it (CacheDir, or bind/var/cache/pacman/pkg). Should remove most of the 396 MiB. Careful: the guest must not WRITE to the host cache, and a stale cached package can be older than the guest's mirror expects —-Syufirst, or accept--neededsemantics. - Trim the package list.
base-develis a large group; check whether the target tests actually need all of it.kcovis only needed for the shell coverage jail. - Cut the cloud-init wait. The build boot spends 45s on datasource
probing that the sealed image then disables anyway; a kernel arg or an
earlier
cloud-init.disabledmay shorten it. - Cache the provisioned disk. If provisioning inputs are unchanged
(
guest/provision.shhash + package list), a rebuild could reuse the last provisioned image instead of redoing it. Only worth it if 1-3 are not enough.
Note the build already ends with a smoke test that boots a real sandbox; keep that. Making the build fast by making it fail open would be a bad trade.
Definition of done
- The 5 items in Job 1 have been run in a sandbox, with results reported per item (pass / fail / not-applicable-in-a-VM, with reasons).
- Every vmbox bug found is fixed, committed, and pushed.
bats ~/utils/vmbox/tests/test_vmbox.bats, shellcheck and ruff all clean.- Either the CLAUDE.md rule is written and agreed, or there is a clear statement of what still makes vmbox untrustworthy.
vm build --forceis measurably faster than the ~10 min baseline, with the before/after numbers stated.
State as of handoff
Verified working: the shutdown verdict (5 outcomes, all measured against real guests), destructive demo 7/7, reset ~2s, VM-to-VM ping (0% loss), screenshots, pinned clock, repo share + clone, base-corruption guard, build smoke gate.
Unverified / not attempted: everything in Job 1 above; anything hardware-bound; concurrent sandboxes beyond two.