Skip to content

fix(nodes): pre-validate storage_dir + unwrap daemon error envelope#96

Open
Nic-dorman wants to merge 2 commits into
mainfrom
fix/storage-dir-writability-probe
Open

fix(nodes): pre-validate storage_dir + unwrap daemon error envelope#96
Nic-dorman wants to merge 2 commits into
mainfrom
fix/storage-dir-writability-probe

Conversation

@Nic-dorman
Copy link
Copy Markdown
Contributor

Summary

Customer reports of `Failed to add nodes: {"error":"I/O error: Access is denied. (os error 5)"}` on Windows after v0.6.7 (the first build where the user-chosen `storage_dir` is actually honored at node-add time). Two problems in one report: the path is unwritable to the daemon, and the toast surfaces the raw JSON envelope verbatim.

Three defences:

  1. `probe_writable` Tauri command (`src-tauri/src/lib.rs`) — writes a sentinel, fsyncs, deletes. Returns the raw OS message + path on failure.
  2. Settings picker gates on the probe. `pages/settings.vue::pickStorageDir` calls `settingsStore.probeStorageDir(path)` before `setStorageDir`. On failure the path is not saved and the error is shown inline under the card and as a global banner in `layouts/default.vue`. Success toast changes from "Storage directory updated" to "Storage directory verified".
  3. Startup re-probe. `app.vue` calls `settingsStore.revalidateStorageDir()` after `loadConfig` so a previously-working path that's gone offline is caught before the user reaches Add Node. Non-blocking; does not clear the saved path.
  4. Envelope unwrap in `utils/daemon-api.ts::request`. Parses `{"error": "..."}` out of 4xx/5xx bodies before throwing. Falls back to the raw body when JSON parses but lacks `.error`, or when the body isn't JSON at all (network / Tauri errors). Covers every daemon error toast.

Common failure categories this catches: volume read-only flag (USB / VHDX / BitLocker To Go), second internal drive with default ACL, OneDrive Files-On-Demand placeholders, restrictive network-share ACL.

Test plan

  • CI vitest (local Win + Node 20.18 hits the known `@vue/test-utils` dev-env issue)
  • Plug in any USB; `diskpart > attributes volume set readonly`. In Settings → Storage Directory pick the USB root. Expect inline error + global banner; path not persisted.
  • Pick a writable folder (e.g. `C:\Users<me>\storage`). Expect "Storage directory verified" toast, banner clears, Add Node succeeds.
  • With a saved unwritable path, eject the USB and relaunch the app. Banner should appear on startup before the user opens Settings.
  • On Linux/macOS: pick a folder with `chmod -w`. Expect the same inline-error path.
  • Trigger any other daemon error (rewards address validation, network port collision) — toast should show the unwrapped daemon message, not a JSON envelope.

🤖 Generated with Claude Code

Nic-dorman and others added 2 commits May 12, 2026 11:10
Two related fixes for the customer-reported Windows toast:

  Failed to add nodes: {"error":"I/O error: Access is denied. (os error 5)"}

The PR #34 release (v0.6.7) was the first build where storage_dir is
actually honored at node-add time — any path that's writable in
Explorer but not to the daemon process (USB read-only volume, OneDrive
placeholder, second-drive default ACL, network share) now produces this
error. The toast also shows the daemon's raw JSON envelope verbatim,
which looks broken even when the underlying error is real.

Three defences:

1. probe_writable Tauri command — writes a tiny sentinel, fsyncs,
   deletes. Surfaces the raw OS message + path on failure.

2. Settings → Storage Directory picker calls probe_writable before
   persisting. On failure, the path is not saved and the error is
   surfaced both inline under the card and as a global banner in the
   default layout. Toast on success is "Storage directory verified"
   instead of the previous unconditional "Storage directory updated".

3. Startup re-probe — app.vue calls settingsStore.revalidateStorageDir()
   after loadConfig so a previously-working path that's gone offline
   (USB unplugged, OneDrive offline, perms changed) is caught before
   the user reaches Add Node. Non-blocking; does not clear the saved
   path.

4. daemon-api request() unwraps the {"error": "..."} envelope from
   4xx/5xx bodies, falling back to the raw body when it isn't JSON or
   doesn't have the expected shape. Covers every daemon error toast in
   the app, not just addNodes.

Tests: 5 cases for the envelope unwrap (JSON envelope, plain string,
unrelated JSON shape, malformed JSON, Error instance with .message).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…level

Blue 'info' was matching the previous "updated" toast, but the new flow
performs a real verification before persisting — green 'success' is the
honest signal that we did work and it succeeded.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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