fix(share): give a clear error when the local mount path is on a read-only filesystem#3235
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (4)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds an exported ChangesMount Directory Writability Validation
Sequence Diagram(s)sequenceDiagram
participant Run as runShareMount
participant Check as checkLocalMountWritable
participant Cleanup as cleanup/tempdir
Run->>Check: validate(localMount)
alt writable
Check-->>Run: { writable: true }
Run->>Run: proceed to sshfs invocation
else not writable
Check-->>Run: { writable: false, reason }
Run->>Cleanup: remove SSH config / temp dir
Run->>Run: process.exit(1)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/share-command.ts`:
- Around line 84-88: The catch on fs.accessSync currently collapses all errors
into "directory is not writable"; change it to inspect the caught error (e.g.,
the thrown Error object in the catch) and preserve the root cause similar to the
mkdirSync branch: if err.code === 'EROFS' return a writable:false result with
the same EROFS/read-only reason, otherwise return writable:false with a generic
"directory is not writable" reason but include the original error message/code
so callers can see the root cause; update the catch block around
fs.accessSync(localMount, fs.constants.W_OK) to implement this conditional error
handling.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 28f1b1df-e7ba-436f-bfa5-b5118f735435
📒 Files selected for processing (4)
.agents/skills/nemoclaw-user-reference/references/commands.mddocs/reference/commands.mdsrc/lib/share-command.tstest/share-command-writable.test.ts
|
✨ Thanks for submitting this PR that fixes the issue with the local mount path being on a read-only filesystem. This change involves adding a check to verify the mount directory is writable before invoking sshfs and provides a structured error message when the path is not writable. Related open issues: |
|
While this PR is looking into improving the Error Reporting of the |
|
Thanks for the feedback @SeseMueller, will file a separate follow-up so this PR stays scoped to the writable-fs fix - stay tuned. Cheers! |
Closes NVIDIA#3414. `nemoclaw <sandbox> share mount` exits non-zero with no stderr when the remote sandbox path does not exist (e.g. a typo). sshfs is silent in this case, so the CLI surfaced only the bare "SSHFS mount failed." line and the user had no signal that the source path was the problem. Reported by @SeseMueller in NVIDIA#3235 as follow-up to the writable-fs error fix. Adds a `checkSandboxPathExists` dep to `ShareCommandDeps` that runs `openshell sandbox exec <name> -- test -e <remotePath>` and treats a zero exit status as "path exists" (everything else is missing or unreachable, which is the safer default for the caller). The default impl reuses the same `OPENSHELL_PROBE_TIMEOUT_MS` budget as the existing `getSshConfig` probe. `assertSandboxPathExistsOrExit` is an exported helper that calls the dep, emits a structured error when the path is missing, and exits 1. `runShareMount` invokes it after `ensureLive` and before `getSshConfig`, so a missing remote path fails fast with: Sandbox path '/sandbox/typo' does not exist in sandbox 'my-assistant'. Verify the path with: nemoclaw my-assistant connect, then ls /sandbox/typo The default is /sandbox; check for typos in any custom path you passed. No SSH config is written, no sshfs binary is invoked, no local mount directory is created when the pre-flight fails. Three behavior tests in `test/share-command-remote-path.test.ts` cover the happy path (no exit, no stderr), the missing-path exit (verifies error contents and that the dep was called with the right args), and the cliName branding hint (so the `nemohermes` alias surfaces `nemohermes hermes connect` rather than `nemoclaw hermes connect`). Signed-off-by: latenighthackathon <support@latenighthackathon.com>
|
Follow-up filed: #3414 (issue) and #3415 (PR) - the missing-remote-path pre-flight you flagged, scoped separately so this PR stays focused on the writable-fs case. @SeseMueller, mind taking a look at #3415 when you have a moment? Cheers! |
b6314b5 to
76b0441
Compare
Closes NVIDIA#3414. `nemoclaw <sandbox> share mount` exits non-zero with no stderr when the remote sandbox path does not exist (e.g. a typo). sshfs is silent in this case, so the CLI surfaced only the bare "SSHFS mount failed." line and the user had no signal that the source path was the problem. Reported by @SeseMueller in NVIDIA#3235 as follow-up to the writable-fs error fix. Adds a `checkSandboxPathExists` dep to `ShareCommandDeps` that runs `openshell sandbox exec <name> -- test -e <remotePath>` and treats a zero exit status as "path exists" (everything else is missing or unreachable, which is the safer default for the caller). The default impl reuses the same `OPENSHELL_PROBE_TIMEOUT_MS` budget as the existing `getSshConfig` probe. `assertSandboxPathExistsOrExit` is an exported helper that calls the dep, emits a structured error when the path is missing, and exits 1. `runShareMount` invokes it after `ensureLive` and before `getSshConfig`, so a missing remote path fails fast with: Sandbox path '/sandbox/typo' does not exist in sandbox 'my-assistant'. Verify the path with: nemoclaw my-assistant connect, then ls /sandbox/typo The default is /sandbox; check for typos in any custom path you passed. No SSH config is written, no sshfs binary is invoked, no local mount directory is created when the pre-flight fails. Three behavior tests in `test/share-command-remote-path.test.ts` cover the happy path (no exit, no stderr), the missing-path exit (verifies error contents and that the dep was called with the right args), and the cliName branding hint (so the `nemohermes` alias surfaces `nemohermes hermes connect` rather than `nemoclaw hermes connect`). Signed-off-by: latenighthackathon <support@latenighthackathon.com>
Closes NVIDIA#3414. `nemoclaw <sandbox> share mount` exits non-zero with no stderr when the remote sandbox path does not exist (e.g. a typo). sshfs is silent in this case, so the CLI surfaced only the bare "SSHFS mount failed." line and the user had no signal that the source path was the problem. Reported by @SeseMueller in NVIDIA#3235 as follow-up to the writable-fs error fix. Adds a `checkSandboxPathExists` dep to `ShareCommandDeps` that runs `openshell sandbox exec <name> -- test -e <remotePath>` and treats a zero exit status as "path exists" (everything else is missing or unreachable, which is the safer default for the caller). The default impl reuses the same `OPENSHELL_PROBE_TIMEOUT_MS` budget as the existing `getSshConfig` probe. `assertSandboxPathExistsOrExit` is an exported helper that calls the dep, emits a structured error when the path is missing, and exits 1. `runShareMount` invokes it after `ensureLive` and before `getSshConfig`, so a missing remote path fails fast with: Sandbox path '/sandbox/typo' does not exist in sandbox 'my-assistant'. Verify the path with: nemoclaw my-assistant connect, then ls /sandbox/typo The default is /sandbox; check for typos in any custom path you passed. No SSH config is written, no sshfs binary is invoked, no local mount directory is created when the pre-flight fails. Three behavior tests in `test/share-command-remote-path.test.ts` cover the happy path (no exit, no stderr), the missing-path exit (verifies error contents and that the dep was called with the right args), and the cliName branding hint (so the `nemohermes` alias surfaces `nemohermes hermes connect` rather than `nemoclaw hermes connect`). Signed-off-by: latenighthackathon <support@latenighthackathon.com>
a60bbdf to
e004f5c
Compare
…-only filesystem Closes NVIDIA#3192. `share mount` projects sandbox files onto a host directory via SSHFS, so the local target must be on a writable filesystem; FUSE itself creates the mount on the host side. When the chosen path is on a read-only fs, sshfs/fusermount3 used to surface a low-level error (`fusermount3: user has no write access to mountpoint`) without explaining what the user can change. Add a `checkLocalMountWritable` helper that recursively creates the mount directory and verifies the resulting path is writable, distinguishing EROFS (parent on read-only fs), EACCES (permission denied), and other failures. `runShareMount` now consults this helper after writing the temporary SSH config and before invoking sshfs, so a non-writable target fails fast with a structured error: the offending path, the underlying reason, and a suggested writable-path invocation. Also notes the writable-mount-target requirement in the `share mount` prerequisites in `docs/reference/commands.md` and the agent-skill mirror so users hitting the default `~/.nemoclaw/mounts/<name>` on a read-only filesystem know to pass an explicit local path. Adds five unit tests covering the success case, EROFS, EACCES, an unexpected mkdir failure, and a pre-existing read-only directory. Signed-off-by: latenighthackathon <support@latenighthackathon.com>
…ting dir The `accessSync` branch in `checkLocalMountWritable` collapsed every error into "directory is not writable", which dropped the EROFS signal on a pre-existing mount target whose filesystem is read-only - a case the `mkdirSync` branch already differentiates. Inspect the caught error's code and report "filesystem is read-only" for EROFS, the generic "directory is not writable" for EACCES, and the underlying message for anything else, mirroring the `mkdirSync` branch shape. Two new test cases cover the EROFS and EACCES preservation paths. Signed-off-by: latenighthackathon <support@latenighthackathon.com>
ecd49ed to
0fa6708
Compare
Closes NVIDIA#3414. `nemoclaw <sandbox> share mount` exits non-zero with no stderr when the remote sandbox path does not exist (e.g. a typo). sshfs is silent in this case, so the CLI surfaced only the bare "SSHFS mount failed." line and the user had no signal that the source path was the problem. Reported by @SeseMueller in NVIDIA#3235 as follow-up to the writable-fs error fix. Adds a `checkSandboxPathExists` dep to `ShareCommandDeps` that runs `openshell sandbox exec <name> -- test -e <remotePath>` and treats a zero exit status as "path exists" (everything else is missing or unreachable, which is the safer default for the caller). The default impl reuses the same `OPENSHELL_PROBE_TIMEOUT_MS` budget as the existing `getSshConfig` probe. `assertSandboxPathExistsOrExit` is an exported helper that calls the dep, emits a structured error when the path is missing, and exits 1. `runShareMount` invokes it after `ensureLive` and before `getSshConfig`, so a missing remote path fails fast with: Sandbox path '/sandbox/typo' does not exist in sandbox 'my-assistant'. Verify the path with: nemoclaw my-assistant connect, then ls /sandbox/typo The default is /sandbox; check for typos in any custom path you passed. No SSH config is written, no sshfs binary is invoked, no local mount directory is created when the pre-flight fails. Three behavior tests in `test/share-command-remote-path.test.ts` cover the happy path (no exit, no stderr), the missing-path exit (verifies error contents and that the dep was called with the right args), and the cliName branding hint (so the `nemohermes` alias surfaces `nemohermes hermes connect` rather than `nemoclaw hermes connect`). Signed-off-by: latenighthackathon <support@latenighthackathon.com>
|
Closing in favor of #3646, which carries the same fix as a single clean signed commit rebased onto current upstream/main (post-v0.0.44). Tested in container: 6/6 checkLocalMountWritable cases pass plus 9/9 existing share-command.test.ts. Cheers! |
<!-- markdownlint-disable MD041 --> ## Summary Closes #3414. `nemoclaw <sandbox> share mount` exits non-zero with no stderr when the remote sandbox path does not exist (e.g. a typo). sshfs is silent in this case, so the CLI surfaced only the bare `SSHFS mount failed.` line and the user had no signal that the source path was the problem. Reported by @SeseMueller in [#3235](#3235 (comment)) as a follow-up to the writable-fs error fix. ## Related Issue Closes #3414 ## Changes - New `checkSandboxPathExists` dep on `ShareCommandDeps` (`src/lib/share-command-deps.ts`). Default impl runs `openshell sandbox exec <name> -- test -e <remotePath>` with the same `OPENSHELL_PROBE_TIMEOUT_MS` budget as the existing `getSshConfig` probe, and treats a zero exit status as "path exists" (everything else is missing or unreachable). - New exported helper `assertSandboxPathExistsOrExit(deps, sandboxName, remotePath)` in `src/lib/share-command.ts`. Consults the dep, emits a structured error when the probe returns non-zero, and exits 1. The error is phrased as a verification failure rather than a definitive "path is missing" claim, because the probe also returns false on transient exec failures (sandbox just restarted, gRPC hiccup), and a wrong-cause message wastes the user's time. Extracted so the behavior is testable without driving the full sshfs lifecycle. - `runShareMount` calls the helper after `ensureLive` and before `getSshConfig`, so a non-verifiable remote path fails fast with: ``` Could not verify sandbox path '/sandbox/typo' in sandbox 'my-assistant' (missing path or probe failure). Verify the path with: nemoclaw my-assistant connect, then ls /sandbox/typo The default is /sandbox; check for typos in any custom path you passed. ``` No SSH config is written, no sshfs binary is invoked, no local mount directory is created when the pre-flight fails. ## Type of Change - [x] Code change (feature, bug fix, or refactor) - [ ] Code change with doc updates - [ ] Doc only ## Verification - [x] `npx prek run --all-files` passes for the staged files (includes the source-shape test budget gate). - [x] Three behavior tests in `test/share-command-remote-path.test.ts` cover the happy path (no exit, no stderr), the missing-path exit (verifies error contents and that the dep was called with the right args), and the `cliName` branding hint (so the `nemohermes` alias surfaces `nemohermes hermes connect` rather than `nemoclaw hermes connect`). - [x] `npx vitest run test/share-command-remote-path.test.ts` - 3/3 pass. - [x] `npx tsx scripts/find-source-shape-tests.ts` - 0 source-shape cases (no source-text assertions). - [x] `npm run build:cli` clean. - [x] No secrets, API keys, or credentials committed. --- Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added pre-flight validation for `share mount` to verify the remote path exists in the sandbox before attempting to connect. * **Bug Fixes** * Mount operations now fail early with helpful guidance if the remote path cannot be verified, preventing connection attempts with invalid paths. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/NVIDIA/NemoClaw/pull/3647?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: latenighthackathon <support@latenighthackathon.com> Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com> Co-authored-by: latenighthackathon <latenighthackathon@users.noreply.github.com> Co-authored-by: Carlos Villela <cvillela@nvidia.com>
Summary
nemoclaw <name> share mountnow fails fast with a structured, actionable error when the local mount path is on a read-only filesystem, instead of leaking the low-levelfusermount3: user has no write access to mountpointline that #3192 reported on AlmaLinux 9.7.Related Issue
Closes #3192
Changes
checkLocalMountWritable(localMount)helper insrc/lib/share-command.tsrecursively creates the mount directory and verifies the resulting path is writable, distinguishing EROFS (parent on read-only fs), EACCES (permission denied), and other failures and returning a structured{ writable, reason }result.runShareMountconsults this helper after writing the temporary SSH config and before invokingsshfs. A non-writable target now exits with the offending path, the underlying reason, and a suggested writable-path invocation; the temporary SSH config file is cleaned up before the early exit so we don't leave an orphan in/tmp. Sample output for a read-only target:Local mount path '/ro/mounts/my-assistant' is not usable: parent filesystem is read-only. 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 my-assistant share mount /sandbox <writable-path>.docs/reference/commands.mdand the agent-skill mirror in.agents/skills/nemoclaw-user-reference/references/commands.mdnow include the writable-mount-target requirement in theshare mountprerequisites, so users hitting the default~/.nemoclaw/mounts/<name>on a read-only filesystem know they can pass an explicit local path as the second positional argument.test/share-command-writable.test.tscovers five cases for the helper: success, EROFS frommkdirSync, EACCES frommkdirSync, an unexpectedmkdirSyncfailure (ENOSPC), and a pre-existing directory whoseaccessSyncfails.Type of Change
Verification
npx prek run --all-filespasses (commitlint, gitleaks, biome, TypeScript, source-shape, skills YAML, dist-sourcemaps)npm test(npx vitest run) passes for the new file: 5/5 intest/share-command-writable.test.ts. Full suite was 3525 passed / 4 pre-existing timing flakes (sandbox-stuck-recovery, cli timing assertion) / 13 skipped; flakes are unrelated toshare-command.tsand pass 4/4 in isolation.Signed-off-by: latenighthackathon latenighthackathon@users.noreply.github.com
Summary by CodeRabbit
Documentation
nemoclaw <name> share mountrequires the local mount path to be on a writable host filesystem and how to supply an explicit writable destination.Bug Fixes
Tests