ruvnet/RuView · error · anyhow::Error

data_dir not accessible {}: {e}

Error message

data_dir not accessible {}: {e}

What it means

`safe_join`, used by every filesystem write in the training module, canonicalizes the session's `data_dir` root to verify containment. If `base.canonicalize()` fails — the directory no longer exists, a component lacks search (execute) permission, or a symlink loop exists — the write is aborted with this error carrying the OS cause.

Source

Thrown at v2/crates/wifi-densepose-pointcloud/src/training.rs:53

/// through user-supplied names.
fn safe_join(base: &Path, child: &str) -> Result<PathBuf> {
    // Reject absolute children and any `..` components up front.
    let child_path = Path::new(child);
    if child_path.is_absolute() {
        return Err(anyhow!("child path must be relative: {child}"));
    }
    for comp in child_path.components() {
        if matches!(comp, std::path::Component::ParentDir) {
            return Err(anyhow!("child path may not contain `..`: {child}"));
        }
    }

    let joined = base.join(child_path);
    // Canonicalise base (must exist) and verify joined starts with it. If the
    // joined file doesn't exist yet we canonicalise the parent.
    let canonical_base = base
        .canonicalize()
        .map_err(|e| anyhow!("data_dir not accessible {}: {e}", base.display()))?;
    let canonical_parent = joined
        .parent()
        .ok_or_else(|| anyhow!("no parent for {}", joined.display()))?;
    let canonical_parent = canonical_parent
        .canonicalize()
        .map_err(|e| anyhow!("parent not accessible {}: {e}", canonical_parent.display()))?;
    if !canonical_parent.starts_with(&canonical_base) {
        return Err(anyhow!(
            "refusing to write outside data_dir: {}",
            joined.display()
        ));
    }
    Ok(canonical_parent.join(
        joined
            .file_name()
            .ok_or_else(|| anyhow!("no filename for {}", joined.display()))?,
    ))
}

View on GitHub (pinned to 4685618388)

Solutions

  1. Recreate the directory and rebuild the session: `mkdir -p <data_dir>` then a fresh `TrainingSession::new(...)`.
  2. Verify the dir is searchable by the same user running the session: `sudo -u <user> ls <data_dir>`.
  3. For removable/network roots, confirm the mount is present before issuing writes.

Example fix

// before
session.save()?; // data_dir deleted underneath -> "data_dir not accessible"
// after
if !std::path::Path::new(&dir).is_dir() {
    std::fs::create_dir_all(&dir)?;
}
let session = TrainingSession::new(&dir)?;
session.save()?;
Defensive patterns

Strategy: validation

Validate before calling

// Before any write API on a long-lived session.
fn data_dir_alive(dir: &std::path::Path) -> bool {
    dir.is_dir() && dir.canonicalize().is_ok()
}

Try / catch

match session.append(frame) {
    Err(e) if e.downcast_ref::<std::io::Error>()
        .map(|io| io.kind() == std::io::ErrorKind::NotFound)
        .unwrap_or(false) =>
    {
        // data_dir vanished: recreate and rebuild the session, then retry once
        std::fs::create_dir_all(dir)?;
        let session = TrainingSession::new(&dir)?;
        session.append(frame)
    }
    r => r,
}

Prevention

When it happens

Trigger: The data dir was deleted after `TrainingSession::new` succeeded; a network mount (NFS/SMB) dropped between session creation and a write; permission revocation on an ancestor directory; symlink cycles inside the path.

Common situations: `rm -rf` of the output dir mid-session (common in test harnesses); unmounted USB/NFS roots; running the writer under a different uid than the creator; container volume remounts.

Related errors


AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16). Data as JSON: /api/errors/34d9f3850d89b980. Report an issue: GitHub.