Y2Z/monolith · critical

unable to read file

Error message

unable to read file

What it means

In retrieve_asset, when a file:// URL resolves to an existing regular file, the code calls fs::read(path).expect("unable to read file"). If the read fails despite the file existing (permission denied, it is a special file like a FIFO/device, it was deleted between the exists() check and the read, or I/O error), the expect panics, unwinding the process rather than returning the reqwest::Error the function signature promises.

Source

Thrown at src/session.rs:98

                self.client.get("").send()?;
            }

            let path_buf: PathBuf = url.to_file_path().unwrap().clone();
            let path: &Path = path_buf.as_path();
            if path.exists() {
                if path.is_dir() {
                    if !self.options.silent {
                        print_error_message(&format!("{} (is a directory)", &cache_key));
                    }

                    // Provoke error
                    Err(self.client.get("").send().unwrap_err())
                } else {
                    if !self.options.silent {
                        print_info_message(&cache_key.to_string());
                    }

                    let file_blob: Vec<u8> = fs::read(path).expect("unable to read file");

                    Ok((
                        file_blob.clone(),
                        url.clone(),
                        detect_media_type(&file_blob, url),
                        "".to_string(),
                    ))
                }
            } else {
                if !self.options.silent {
                    print_error_message(&format!("{} (file not found)", &url));
                }

                // Provoke error
                Err(self.client.get("").send().unwrap_err())
            }
        } else if self.cache.is_some() && self.cache.as_ref().unwrap().contains_key(&cache_key) {
            // URL is in cache, we get and return it

View on GitHub (pinned to a6fc8d0095)

Solutions

  1. Check that the process user has read permission on the target path (ls -l, or run as a user with access) before invoking the tool
  2. Verify the file:// URL points to a regular readable file, not a device/fifo or dangling symlink
  3. Pre-check with std::fs::metadata(path).map(|m| m.is_file()) and File::open in calling code to fail gracefully before retrieve_asset
  4. Patch the code to replace expect with match fs::read(path) and map the io::Error into the function's Err type

Example fix

// before
let file_blob: Vec<u8> = fs::read(path).expect("unable to read file");
// after
let file_blob: Vec<u8> = fs::read(path).map_err(|e| {
    eprintln!("unable to read file {}: {}", path.display(), e);
    self.client.get("").send().unwrap_err()
})?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_readable_file(url: &url::Url) -> bool {
    url.to_file_path().ok()
        .and_then(|p| std::fs::metadata(p).ok())
        .map(|m| m.is_file())
        .unwrap_or(false)
        && url.to_file_path().ok()
            .map(|p| std::fs::File::open(p).is_ok())
            .unwrap_or(false)
}

Type guard

fn is_readable_regular_file(path: &std::path::Path) -> bool {
    std::fs::metadata(path).map(|m| m.is_file()).unwrap_or(false)
        && std::fs::File::open(path).is_ok()
}

Try / catch

// retrieve_asset returns Result; catch the provoked reqwest::Error and check fs state
match session.retrieve_asset(&parent, &file_url) {
    Ok((blob, _, media_type, _)) => { /* use blob */ }
    Err(e) => {
        let path = file_url.to_file_path().unwrap();
        if !path.exists() { eprintln!("file not found: {}", path.display()); }
        else if std::fs::File::open(&path).is_err() { eprintln!("permission denied: {}", path.display()); }
        else { eprintln!("asset retrieval failed: {}", e); }
    }
}

Prevention

When it happens

Trigger: Calling retrieve_asset (directly or via create_monolithic_document / read_local_file_with_* helpers) with a file:// URL whose path exists and is not a directory but cannot be opened for reading: permission-denied file, unreadable special file (e.g. /dev/... mapped via file://), or a race where the file disappears between path.exists() and fs::read.

Common situations: Running monolith as a different user than the file owner (e.g. in CI or a container with restricted permissions); pointing at files under a mounted volume with no read bit; a symlink pointing to a deleted target; TOCTOU deletion on a tmp file.

Related errors


AI-assisted analysis of Y2Z/monolith@a6fc8d0095 (2026-09-05). Data as JSON: /api/errors/013f1652ccfdfd43. Report an issue: GitHub.