Skip to content

perl-vfs: fix image build overflow that masked ENOSPC as a parent-dir ENOENT#829

Open
brandonpayton wants to merge 1 commit into
mainfrom
gascity/kd-1mr/kd-n6vr-perl-vfs-image-build-fails-with-vfs-parent-dir-write-err
Open

perl-vfs: fix image build overflow that masked ENOSPC as a parent-dir ENOENT#829
brandonpayton wants to merge 1 commit into
mainfrom
gascity/kd-1mr/kd-n6vr-perl-vfs-image-build-fails-with-vfs-parent-dir-write-err

Conversation

@brandonpayton

Copy link
Copy Markdown
Member

Purpose

Building the browser Perl VFS image (bash images/vfs/scripts/build-perl-vfs-image.sh) crashed with SFSError: No such file or directory at pathResolveParent, and no perl.vfs.zst was produced (kd-n6vr; discovered during kd-gtxa browser verification). This restores the build and, more importantly, makes the underlying failure mode honest.

Root cause (a product bug, not a perl-src artifact)

The builder allocated a fixed, non-growable 16 MiB SharedArrayBuffer, but the Perl 5.40.3 stdlib payload is ~25.68 MiB across 1288 files (+238 dirs). The in-memory VFS ran out of space mid-write; the ENOSPC from mkdir in the parent-dir loop was silently swallowed by ensureDir's catch-all, so the directory was never created and the failure resurfaced far away as a misleading ENOENT when the next file write could not resolve its missing parent.

Static analysis of the merged PRIVLIB namespace reproduced exactly 1288 included paths and found zero file/dir name collisions, ruling out the perl-src-layout hypothesis. pathResolveParent threw -2 (ENOENT) (parent path unresolved), not -20 (ENOTDIR) (file-in-the-way) — consistent with a masked, never-created parent.

Changes

  • images/vfs/scripts/build-perl-vfs-image.ts — allocate a growable SharedArrayBuffer(64 MiB, { maxByteLength: 256 MiB }) + create(sab, 256 MiB), matching the mariadb/redis/sqlite image builders. The full stdlib fits (and can grow); the empty tail compresses away (64 MB raw → 3.4 MB zstd).
  • host/src/vfs/image-helpers.tsensureDir and symlink now swallow only EEXIST and rethrow every other error, so a real failure (ENOSPC/ENOTDIR/ENOENT) is reported at its true origin instead of being masked. Per CLAUDE.md: prefer truthful failure over convenient illusion.
  • host/test/vfs-image.test.ts — 4 regression tests pinning the honest-failure contract.

Verification

  • Original crash reproduced in-worktree (changes stashed): exact ENOENT at pathResolveParent, code -2, while "Writing 1288 files to VFS".
  • Fix rebuilds the image: all 1288 files written; apps/browser-demos/public/perl.vfs.zst produced (64.0 MB raw → 3.4 MB zstd).
  • Host vitest gate (cd host && npx vitest run): 586 passed / 29 failed / 204 skipped with the fix vs 582 / 29 / 201 on a clean baseline → +4 passed (the new tests), 0 new failures. The 29 failures are an identical pre-existing set (kernel/spawn/pthread/wasm/wasi suites needing built kernel.wasm/example binaries absent from this worktree) — unrelated to this change.

Durable evidence: test-runs/kd-n6vr/DISPOSITION.md and test-runs/kd-n6vr/outcome-lists/.

Scope / follow-ups

  • Fixes building perl.vfs.zst; booting perl in-browser remains kd-yuef/kd-gtxa scope (gated on trusted-CI GHCR bottle publish). No browser smoke run here.
  • The erlang (16 MiB) and python (32 MiB) VFS builders still use non-growable buffers; with the now-honest ensureDir a future overflow there fails loudly as ENOSPC. Candidate follow-up to adopt the growable pattern proactively.

🤖 Generated with Claude Code

… ENOENT

The Perl 5.40 stdlib payload (~26 MiB across 1288 files) overflowed the fixed
16 MiB SharedArrayBuffer in build-perl-vfs-image.ts. The out-of-space error
from mkdir was swallowed by ensureDir's catch-all, so it resurfaced as a
confusing "No such file or directory" (ENOENT) at pathResolveParent when a
later write could not resolve its never-created parent (kd-n6vr; discovered in
kd-gtxa browser verification). No file/dir name collision exists in the merged
stdlib namespace, ruling out a perl-src-state artifact.

- build-perl-vfs-image.ts: use a growable SharedArrayBuffer (64 MiB init,
  256 MiB max) like the mariadb/redis/sqlite image builders, so the full
  stdlib fits and can grow. perl.vfs.zst is produced again (64 MB raw ->
  3.4 MB zstd).
- image-helpers.ts: ensureDir and symlink now swallow only EEXIST and rethrow
  every other error (ENOSPC/ENOTDIR/ENOENT), so a real failure is reported at
  its true origin instead of being masked -- per CLAUDE.md "prefer truthful
  failure over convenient illusion".
- vfs-image.test.ts: regression tests for the honest-failure contract.

Verification: original crash reproduced in-worktree; fix rebuilds perl.vfs.zst;
4 new host tests pass; full host vitest shows +4 passed / 0 new failures vs a
clean baseline (29 pre-existing kernel/wasm failures identical).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

Phase B-1 matrix build status — pr-829-staging

ABI v16. 67 built, 0 failed, 67 total.

Package Arch Status Sha
libcurl wasm32 built b01be826
libcxx wasm32 built bd2dd5a6
libcxx wasm64 built 926cfc96
libpng wasm32 built 25e56ef0
libxml2 wasm32 built eb77d16a
libxml2 wasm64 built 9743c3c9
openssl wasm32 built 296793ae
openssl wasm64 built b53a16d9
sqlite wasm32 built 93b98c80
sqlite wasm64 built 7079a1ac
zlib wasm32 built 4543740c
zlib wasm64 built 4ccf5221
bc wasm32 built 0c54e287
bzip2 wasm32 built 614536d6
coreutils wasm32 built e1a33298
curl wasm32 built ecbc3967
dash wasm32 built 4f2caf8b
diffutils wasm32 built 34f174f8
dinit wasm32 built 97a7849b
fbdoom wasm32 built 3799bb5e
file wasm32 built 535dd689
findutils wasm32 built 4b727dbf
gawk wasm32 built 92e5184d
git wasm32 built 42b4d1cb
grep wasm32 built fd4d79fa
gzip wasm32 built 8f54d8ac
kandelo-sdk wasm32 built 3829dcd2
kernel wasm32 built 917959d2
less wasm32 built 7f3184f8
lsof wasm32 built 4dc5ae5b
m4 wasm32 built 5fbbe8b3
make wasm32 built 57a6d854
mariadb wasm32 built 9ed4aa94
mariadb wasm64 built eaf29411
modeset wasm32 built d9f12284
msmtpd wasm32 built 23172a3a
nano wasm32 built 763f56f8
ncurses wasm32 built a7862ee0
netcat wasm32 built 57bdd0cd
nginx wasm32 built 933bdcd3
php wasm32 built e047ea3c
posix-utils-lite wasm32 built 2aec933c
sed wasm32 built 9d958a03
spidermonkey wasm32 built ef0ac7d5
tar wasm32 built 050e7cb3
tcl wasm32 built ee9ae67a
unzip wasm32 built b176c19f
userspace wasm32 built a9e013a2
vim wasm32 built 8194ff88
wget wasm32 built 378dca44
xz wasm32 built 763b854c
zip wasm32 built 1c834314
zstd wasm32 built bcd693e5
bash wasm32 built d435c352
mariadb-test wasm32 built 74e71062
mariadb-vfs wasm32 built 85427981
mariadb-vfs wasm64 built 6c3ac528
nethack wasm32 built 5b7fc658
node wasm32 built cdad27ac
spidermonkey-node wasm32 built 6d8dc738
vim-browser-bundle wasm32 built ef862c6f
nethack-browser-bundle wasm32 built d4992e6b
rootfs wasm32 built ae3df667
shell wasm32 built 82682bda
lamp wasm32 built 43815b7e
node-vfs wasm32 built fd9e7949
wordpress wasm32 built a1d15adb

Auto-generated; replaced on each push. Raw data in the publish-status workflow artifact.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant