tonhowtf/omniget · error

Failed to read .torrent file

Error message

Failed to read .torrent file: {}

What it means

When the download `url` is not a magnet URI, download treats it as a path to a .torrent file. It checks that the path exists and has extension ".torrent", then reads the bytes with tokio::fs::read; any I/O error during that read is wrapped in this error with the OS reason. The file passed the exists/extension check but could not actually be read.

Solutions

  1. Read the wrapped `{}` OS error — permission denied vs not-found dictates the fix.
  2. Verify the .torrent file still exists and is readable by the app user (ls -l / icacls).
  3. Confirm the path is a regular file, not a directory, and is on a mounted, available volume.
  4. Handle the error in the caller by prompting the user to re-select the .torrent file.
  5. Prefer opening the file once (File::open) and reporting an explicit 'file missing' error instead of relying on the exists() check.

Example fix

// before
let bytes = tokio::fs::read(path).await
    .map_err(|e| anyhow::anyhow!("Failed to read .torrent file: {}", e))?;
// after
let bytes = tokio::fs::read(path).await.map_err(|e| {
    if e.kind() == std::io::ErrorKind::NotFound {
        anyhow::anyhow!("Torrent file no longer exists: {}", path.display())
    } else {
        anyhow::anyhow!("Failed to read .torrent file: {}", e)
    }
})?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust: validate the torrent path before calling download
fn validate_torrent_path(path: &str) -> Result<(), String> {
    let p = std::path::Path::new(path);
    if !p.is_file() { return Err(format!("not a regular file: {}", path)); }
    if p.extension().map(|e| e != "torrent").unwrap_or(true) { return Err("extension is not .torrent".into()); }
    std::fs::File::open(p).map(|_| ()).map_err(|e| format!("unreadable: {}", e))
}

Try / catch

match download(path, opts).await {
    Err(e) if e.to_string().contains("Failed to read .torrent file") => {
        // file vanished or unreadable; prompt user to re-select the .torrent
        ui.prompt_reselect_torrent(path);
        Err(e)
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling download with a local .torrent path where tokio::fs::read fails — permission denied, file deleted between the exists() check and the read, path is a directory named x.torrent, or an IO error on a network/removable drive.

Common situations: File moved/deleted after being added to the UI (TOCTOU between exists() and read()); downloading on a machine where the .torrent lives on an unavailable drive; restrictive ACLs; passing a directory or a file without read permission.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12). Data as JSON: /api/errors/af7739583ad66350. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/omniget-core/src/platforms/magnet.rs:178

                *dir_guard = Some(output_dir.clone());
                s
            } else {
                tracing::info!("[magnet] reusing existing session");
                guard.as_ref().unwrap().clone()
            }
        };

        let add_torrent = if url.starts_with("magnet:")
            || url.starts_with("http://")
            || url.starts_with("https://")
        {
            AddTorrent::from_url(url)
        } else {
            let path = std::path::Path::new(url);
            if path.exists() && path.extension().map(|e| e == "torrent").unwrap_or(false) {
                let bytes = tokio::fs::read(path)
                    .await
                    .map_err(|e| anyhow::anyhow!("Failed to read .torrent file: {}", e))?;
                AddTorrent::from_bytes(bytes)
            } else {
                AddTorrent::from_url(url)
            }
        };
        let only_files = opts
            .torrent_files
            .as_ref()
            .filter(|v| !v.is_empty())
            .cloned();
        if let Some(sel) = &only_files {
            tracing::info!("[magnet] selective download: {} file(s)", sel.len());
        }
        let trackers = if opts.torrent_auto_trackers {
            let list = crate::core::trackers::extra_trackers();
            tracing::info!("[magnet] injecting {} public trackers", list.len());
            Some(list)
        } else {

View on GitHub (pinned to 8600b91f42)