Description
nemoclaw <name> share mount /sandbox <target> is supposed to detect when the local target's parent filesystem is read-only and print the message "Local mount path '<...>' is not usable: parent filesystem is read-only." (the EROFS branch in share-command.ts). In practice the user sees "Local mount path '<...>' is not usable: ENOENT: no such file or directory, mkdir '<...>'." — a misleading message that points the user at "the directory is missing" rather than the real cause "the parent filesystem is read-only." Other behavior (exit code 1, follow-up guidance lines, no SSHFS invocation, no temp-file orphans) is correct.
Root cause traced to Node.js fs behavior — see Code Analysis.
Environment
Device: Two Ubuntu 24.04 hosts:
- a1u2n2g-0096-02 / 10.176.178.129 (no GPU)
- 2u2g-gen-0690 / 10.57.211.27 (RTX 5090)
OS: Ubuntu 24.04.4 LTS
Architecture: x86_64
Node.js: v22.22.3
npm: 10.9.8
Docker: Docker version 29.4.1, build 055a478
OpenShell CLI: openshell 0.0.44
NemoClaw: nemoclaw v0.0.52
OpenClaw: 2026.4.24 / 2026.5.22
Steps to Reproduce
- Have any healthy sandbox onboarded.
- Create a genuinely read-only directory on the host:
RO=/tmp/share-mount-ro-$(date +%s)
mkdir -p $RO
sudo mount -o ro,bind $RO $RO
touch $RO/probe # expect: "Read-only file system"
- Run:
nemoclaw <name> share mount /sandbox $RO/mnt
- Inspect line 1 of stderr.
Expected Result
Line 1 of the error should read (per share-command.ts:127):
Local mount path '<...>' is not usable: parent filesystem is read-only.
Followed by the existing follow-up lines:
- "share mount projects sandbox files onto a host directory via SSHFS,"
- "so the local target must be on a writable filesystem."
- "Pick a writable directory: nemoclaw share mount /sandbox "
Exit code 1.
Actual Result
$ nemoclaw ollama-base share mount /sandbox /tmp/share-mount-ro-XXX/mnt
Local mount path '/tmp/share-mount-ro-XXX/mnt' is not usable: ENOENT: no such file or directory, mkdir '/tmp/share-mount-ro-XXX/mnt'.
share mount projects sandbox files onto a host directory via SSHFS,
so the local target must be on a writable filesystem.
Pick a writable directory: nemoclaw ollama-base share mount /sandbox <writable-path>
Exit code: 1
(Identical message on the no-GPU and GPU hosts; the wrong errno is the only difference from spec.)
Code Analysis
src/lib/share-command.ts (lines 122–138):
export function checkLocalMountWritable(localMount: string): { writable: boolean; reason?: string } {
try {
fs.mkdirSync(localMount, { recursive: true }); // ← (A)
} catch (err: unknown) {
const code = (err as NodeJS.ErrnoException | undefined)?.code;
if (code === "EROFS") return { writable: false, reason: "parent filesystem is read-only" };
if (code === "EACCES") return { writable: false, reason: "permission denied creating the directory" };
return { writable: false, reason: err instanceof Error ? err.message : String(err) }; // ← (B)
}
...
}
// Caller (line ~207):
const writable = checkLocalMountWritable(localMount);
if (!writable.writable) {
console.error(` Local mount path '${localMount}' is not usable: ${writable.reason}.`);
...
}
Root cause: Node.js's fs.mkdirSync(path, { recursive: true }) (Node 22 / line A above) does NOT propagate the raw EROFS errno from the kernel. When the parent directory's filesystem is read-only and the child does not exist, the recursive mkdir path returns code: "ENOENT" instead of EROFS. The EROFS branch therefore never fires; the fallback at line B emits the raw "ENOENT: no such file or directory, mkdir '...'" message.
Verified on the failing box (Ubuntu 24.04, Node v22.22.3) with the same RO bind mount:
$ python3 -c "import os, errno; os.mkdir('$RO/mnt')"
# → OSError: [Errno 30] Read-only file system (kernel returns EROFS — correct)
$ node -e "try { require('fs').mkdirSync('$RO/mnt'); } catch (e) { console.log(e.code) }"
# → EROFS (Node non-recursive — correct)
$ node -e "try { require('fs').mkdirSync('$RO/mnt', { recursive: true }); } catch (e) { console.log(e.code) }"
# → ENOENT (Node recursive — masks EROFS)
So the kernel and Node-without-recursive both correctly report EROFS; only { recursive: true } swallows it.
Suggested fix (any one suffices):
a. Drop { recursive: true } for the leaf-only case: if path.dirname(localMount) already exists, call fs.mkdirSync(localMount) without recursive; only fall back to recursive when the parent really is missing. EROFS will then propagate correctly.
b. Before calling mkdir, fs.statSync(path.dirname(localMount)) and check the mounted filesystem's flags (or attempt a write probe). If RO, short-circuit and return the same "parent filesystem is read-only" message without invoking mkdir.
c. Catch ENOENT in checkLocalMountWritable, then fs.statSync(parent) and a write probe — if the parent is RO, rewrite the reason to "parent filesystem is read-only".
Option (a) is the smallest diff and matches the existing EROFS branch the author already wrote.
Logs
Not captured (CLI surface issue; failure is in the mkdir preflight before SSHFS is invoked, so no log file is produced).
NVB#6229505
Description
nemoclaw <name> share mount /sandbox <target>is supposed to detect when the local target's parent filesystem is read-only and print the message "Local mount path '<...>' is not usable: parent filesystem is read-only." (the EROFS branch inshare-command.ts). In practice the user sees "Local mount path '<...>' is not usable: ENOENT: no such file or directory, mkdir '<...>'." — a misleading message that points the user at "the directory is missing" rather than the real cause "the parent filesystem is read-only." Other behavior (exit code 1, follow-up guidance lines, no SSHFS invocation, no temp-file orphans) is correct.Root cause traced to Node.js fs behavior — see Code Analysis.
Environment
Steps to Reproduce
Expected Result
Line 1 of the error should read (per
share-command.ts:127):Followed by the existing follow-up lines:
Exit code 1.
Actual Result
(Identical message on the no-GPU and GPU hosts; the wrong errno is the only difference from spec.)
Code Analysis
src/lib/share-command.ts(lines 122–138):Root cause: Node.js's
fs.mkdirSync(path, { recursive: true })(Node 22 / line A above) does NOT propagate the raw EROFS errno from the kernel. When the parent directory's filesystem is read-only and the child does not exist, the recursive mkdir path returnscode: "ENOENT"instead of EROFS. The EROFS branch therefore never fires; the fallback at line B emits the raw "ENOENT: no such file or directory, mkdir '...'" message.Verified on the failing box (Ubuntu 24.04, Node v22.22.3) with the same RO bind mount:
So the kernel and Node-without-recursive both correctly report EROFS; only
{ recursive: true }swallows it.Suggested fix (any one suffices):
a. Drop
{ recursive: true }for the leaf-only case: ifpath.dirname(localMount)already exists, callfs.mkdirSync(localMount)without recursive; only fall back to recursive when the parent really is missing. EROFS will then propagate correctly.b. Before calling mkdir,
fs.statSync(path.dirname(localMount))and check the mounted filesystem's flags (or attempt a write probe). If RO, short-circuit and return the same "parent filesystem is read-only" message without invoking mkdir.c. Catch ENOENT in
checkLocalMountWritable, thenfs.statSync(parent)and a write probe — if the parent is RO, rewrite the reason to "parent filesystem is read-only".Option (a) is the smallest diff and matches the existing EROFS branch the author already wrote.
Logs
Not captured (CLI surface issue; failure is in the mkdir preflight before SSHFS is invoked, so no log file is produced).
NVB#6229505