Summary
WASM shell commands (cat, ls, etc.) fail when reading from createHostDirBackend mounts. The kernel API (vm.readFile() / vm.writeFile()) and shell writes work correctly -- only WASI reads on mounted paths are broken.
The root cause: WASM binaries use Node.js native WASI whose preopens only maps /workspace to process.cwd(). Kernel mount points (e.g., /mnt/session) are invisible to the WASI runtime because it bypasses the kernel VFS entirely.
Reproduction
import { AgentOs, createHostDirBackend } from "@rivet-dev/agent-os-core";
import common from "@rivet-dev/agent-os-common";
import fs from "node:fs";
fs.mkdirSync("/tmp/test-host/workspace", { recursive: true });
const vm = await AgentOs.create({
software: [common],
mounts: [
{
path: "/mnt/session",
driver: createHostDirBackend({ hostPath: "/tmp/test-host", readOnly: false }),
},
],
});
// Kernel API -- works
await vm.writeFile("/mnt/session/workspace/hello.txt", "hello world");
const content = new TextDecoder().decode(
await vm.readFile("/mnt/session/workspace/hello.txt")
);
console.log(content); // "hello world"
// Shell write -- works
await vm.exec('echo "from shell" > /mnt/session/workspace/shell.txt');
// Host file /tmp/test-host/workspace/shell.txt is created correctly
// Shell read -- FAILS
const catResult = await vm.exec("cat /mnt/session/workspace/hello.txt");
console.log(catResult.stdout); // ""
console.log(catResult.exitCode); // 1
console.log(catResult.stderr);
// "WARN could not retrieve pid for child process"
// "The number ... cannot be converted to a BigInt because it is not an integer"
// Shell ls -- FAILS
const lsResult = await vm.exec("ls /mnt/session/workspace/");
console.log(lsResult.stdout); // ""
console.log(lsResult.exitCode); // 1
// Control: same commands on in-memory VFS work fine
await vm.writeFile("/tmp/control.txt", "ok");
const ctrl = await vm.exec("cat /tmp/control.txt");
console.log(ctrl.stdout); // "ok", exitCode=0
await vm.dispose();
Observed behavior
| Operation |
Result |
vm.writeFile() on mount |
OK |
vm.readFile() on mount |
OK |
vm.exec('echo > file') on mount |
OK |
vm.exec('cat file') on mount |
Empty stdout, exitCode=1, BigInt error |
vm.exec('ls dir/') on mount |
Empty stdout, exitCode=1 |
vm.exec('cat file') on in-memory VFS |
OK |
Where the bug is
buildPreopens() in crates/execution/src/node_import_cache.rs (line 7748) hardcodes a single WASI preopen:
function buildPreopens() {
// ...
return { '/workspace': process.cwd() };
}
This is the only filesystem mapping the WASI runtime receives (line 7895):
const wasi = new WASI({
version: 'preview1',
preopens: buildPreopens(), // only '/workspace'
// ...
});
Kernel mount points like /mnt/session are never passed to the WASI preopens. The kernel API works because Node.js fs calls are intercepted by ESM loader hooks and routed through the kernel VFS via sync RPC (service.rs:8172), which correctly resolves mounts. But WASM binaries issue native WASI syscalls that bypass the kernel entirely.
Additionally, configure_wasm_node_sandbox() in crates/execution/src/wasm.rs (line 598) only adds host filesystem paths (sandbox root, cache, module path) to --allow-fs-read/--allow-fs-write -- mount host paths are not included.
Test coverage gap
No existing test combines vm.exec() with createHostDirBackend mounts:
host-dir-backend.test.ts -- kernel API only
execute.test.ts -- shell commands on in-memory VFS only
native-sidecar-process.test.ts -- mounts via client.readFile() only
Summary
WASM shell commands (
cat,ls, etc.) fail when reading fromcreateHostDirBackendmounts. The kernel API (vm.readFile()/vm.writeFile()) and shell writes work correctly -- only WASI reads on mounted paths are broken.The root cause: WASM binaries use Node.js native WASI whose
preopensonly maps/workspacetoprocess.cwd(). Kernel mount points (e.g.,/mnt/session) are invisible to the WASI runtime because it bypasses the kernel VFS entirely.Reproduction
Observed behavior
vm.writeFile()on mountvm.readFile()on mountvm.exec('echo > file')on mountvm.exec('cat file')on mountvm.exec('ls dir/')on mountvm.exec('cat file')on in-memory VFSWhere the bug is
buildPreopens()incrates/execution/src/node_import_cache.rs(line 7748) hardcodes a single WASI preopen:This is the only filesystem mapping the WASI runtime receives (line 7895):
Kernel mount points like
/mnt/sessionare never passed to the WASI preopens. The kernel API works because Node.jsfscalls are intercepted by ESM loader hooks and routed through the kernel VFS via sync RPC (service.rs:8172), which correctly resolves mounts. But WASM binaries issue native WASI syscalls that bypass the kernel entirely.Additionally,
configure_wasm_node_sandbox()incrates/execution/src/wasm.rs(line 598) only adds host filesystem paths (sandbox root, cache, module path) to--allow-fs-read/--allow-fs-write-- mount host paths are not included.Test coverage gap
No existing test combines
vm.exec()withcreateHostDirBackendmounts:host-dir-backend.test.ts-- kernel API onlyexecute.test.ts-- shell commands on in-memory VFS onlynative-sidecar-process.test.ts-- mounts viaclient.readFile()only