Skip to content

[Ubuntu 24.04][CLI&UX] share mount RO-target diagnostic reports ENOENT instead of "parent filesystem is read-only" (recursive mkdir masks EROFS) #4311

@hulynn

Description

@hulynn

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

  1. Have any healthy sandbox onboarded.
  2. 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"
  3. Run:
    nemoclaw <name> share mount /sandbox $RO/mnt
  4. 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

Metadata

Metadata

Assignees

Labels

NV QABugs found by the NVIDIA QA Teamneeds: triageAwaiting maintainer classification

Type

No fields configured for Bug.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions