Root-caused, fixed, gated, and deployed. bzip2 (and any distinct non-init binary) now execs, runs, and returns to the prompt in a real browser on the production build.
Root cause — not a fetch, not scheduling: an exec-in-place LinkError with no kernel-side exit
Reproduced in headless Chrome on the live dmitry/bzip2-glibc with full console + network + __hwjsLog capture. The per-exec FUSE fetch was healthy — the kernel staged all 2 649 147 bytes of /usr/bin/bzip2:
[proc32] wasm32_user_exec_load: staged /usr/bin/bzip2 (2649147 bytes) at kmem offset 0x1800000 for in-place exec (pid=32)
hardwarejs: user[32] ERROR: WebAssembly.instantiate(): Import #0 "env" "user_memory":
memory import has 201 pages which is smaller than the declared initial of 256
hardwarejs: user[32] exited with code -1 ← and the shell hangs forever
The exec-in-place path re-instantiates the NEW image against the process's existing env.user_memory, which was sized for the OLD image: the A-5.a small-initial (2 MiB) + grow-to-fit runs at spawn time only (kernelSpawnHandler.initUserMemoryPartition). The busybox shell's Memory was 201 pages by exec time; bzip2 declares a 256-page (16 MiB) initial → WebAssembly.instantiate threw a LinkError. That rejection killed the Worker host-side with no kernel-side task exit, so the parent's wait4 never completed — the silent hang, on every invocation.
- Why only distinct binaries: fork is a delta=0 copy of the same module (memory sized to the parent's — always fits); busybox→busybox execs re-instantiate an image with the same declared minimum. Only a distinct image with a larger declared initial trips it. bzip2 was the machine's first.
- Why node "didn't reproduce":
run-machine.mjs/the older gates allocate 64–128 MiB initial user SABs, which already satisfy any image's minimum. At the live LaunchMachine sizing (userInitBytes: 2 MiB) node reproduces the hang byte-for-byte (the new gate proves it).
- The 404s: the console 404 was indeed the PWA-manifest red herring you identified (now also fixed — the
<link rel="manifest"> is root-absolute, brintos-web 99fe3c2); the blob store and /fs/download keying were never at fault.
Fix (hardwarejs 42d3ff3, joel-dev)
ensureUserMemoryFitsImage (src/worker.mjs, + parseImportedMemoryMinPages in src/dynLoader.mjs): before the exec's point of no return — on both the HWJS-B-2 inline (glibc) and park-serviced (musl) legs — parse the staged image's declared env.user_memory minimum and grow the Memory to fit. An un-satisfiable minimum (past userMaxBytes) fails the execve with an attributed -ENOMEM: the shell prints Cannot allocate memory and lives.
- imageLoop belt (
src/worker.mjs): any residual user-image COMPILE/INSTANTIATE failure is now loud and reaped — a re-exec failure posts the attributed diagnostic and exits 127 through the normal tail (task_exit_cleanup + zombie + wait4 wake); a spawn failure keeps the legacy kind:'error' contract but drives the kernel child-exit first. Never a silent hang.
- Partition re-init on exec (
src/kernelSpawnHandler.ts exec-image-installed): the F30 brk/mmap partition is re-derived from the NEW image's __heap_base (monotonic bump), so a larger exec'd image's mmaps can't be served from inside its own .data/.bss/arena span.
RED → GREEN
Committed gate test/hw6-distinct-exec-grow.test.ts: boots the real 15 MB glibc-busybox rootfs at the live sizing (2 MiB initial, cpu=2) and fork+execs a byte-patched busybox whose declared minimum is raised to 512 pages (distinct module, larger initial — the exact #6 shape).
- RED proven flipping on pre-fix
eb6f186: memory import has 201 pages which is smaller than the declared initial of 512, no output, no reap — the ticket's hang, verbatim, under node.
- GREEN on
42d3ff3: exec completes (HW6EXEC42END), prompt returns, the grow dmesg witness is asserted (its absence fails the gate — non-vacuous), and the never-silent arm (an 8192-page minimum past the 256 MiB ceiling) refuses -ENOMEM with a returning prompt.
- Full hardwarejs suite: no regressions (the only failures are pre-existing at
eb6f186, identical pre/post).
Live (authoritative): deployed to production (make web && make web-sync + pnpm sst deploy --stage production), then headless Chrome on https://brintos.io/dmitry/bzip2-glibc:
bzip2 --help prints the full help text; prompt returns.
echo hw6-roundtrip-payload > /tmp/x; bzip2 -c /tmp/x > /tmp/xb; bunzip2 -c /tmp/xb → payload recovered byte-identical; prompt returns.
- Grow witness live:
hwjs exec pid=32 exec-load (inline): grew user_memory 201 -> 256 pages for the exec image's declared minimum (hardwarejs#6).
- Boot, busybox applets, and fork all unchanged-healthy; the manifest 404 is gone.
SHAs: hardwarejs 42d3ff3 · superproject gitlink 04eb7aa · brintos-web 99fe3c2 (all joel-dev). Closing.