tonhowtf/omniget · error · anyhow::Error

rename .part to final failed

Error message

rename .part to final failed: {}

What it means

This error is thrown in HttpFetcher::download after all chunks have been written to the `<file>.part` file and the fetcher attempts to atomically rename the .part file to the final output path. tokio::fs::rename failed, so the completed download data exists in the .part file but was not moved into place. The library returns the underlying io::Error message verbatim.

Solutions

  1. Check that the output directory exists and is writable before starting the download (create it with create_dir_all).
  2. On Windows, close any process (editor/antivirus/backup) that has the destination file open, or retry after the lock is released.
  3. Verify free disk space and that the target filesystem is not mounted read-only.
  4. Ensure output_path is a file path, not an existing directory, and that .part and final paths are on the same filesystem.
  5. Recover manually: the completed data is still in the .part file next to output_path — rename it yourself or resume/retry the download.

Example fix

// before
let fetcher = HttpFetcher::new(url, "/mnt/readonly/out.zip");

// after
let out = Path::new("/mnt/data/out.zip");
tokio::fs::create_dir_all(out.parent().unwrap()).await?;
let fetcher = HttpFetcher::new(url, out.to_path_buf());
Defensive patterns

Strategy: try-catch

Validate before calling

let out = Path::new(&output_path);
let dir = out.parent().ok_or("no parent dir")?;
tokio::fs::create_dir_all(dir).await?;
let md = tokio::fs::metadata(dir).await?;
if md.permissions().readonly() {
    return Err(anyhow!("output dir not writable"));
}
if tokio::fs::metadata(out).await.map(|m| m.is_dir()).unwrap_or(false) {
    return Err(anyhow!("output path is a directory"));
}

Type guard

fn valid_output_target(p: &Path) -> bool {
    p.parent().is_some() && !p.is_dir()
}

Try / catch

match fetcher.download(&mut tx).await {
    Err(e) if e.to_string().contains("rename .part to final failed") => {
        // recover: completed data is still in the .part file
        let part = output_path.with_extension("part");
        tokio::fs::rename(&part, &output_path).await
            .or_else(|_| retry_download().await)?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: tokio::fs::rename(&part_path, &self.output_path) returns Err after a successful download. On Unix this happens when the output directory no longer exists, permission is denied on the target directory, output_path is an existing non-empty directory, or the filesystem is full/read-only. On Windows it also fails if the destination file is locked/open by another process.

Common situations: Output directory deleted or moved while a long download ran; antivirus or another process holding the target file open (Windows); writing to a read-only or full disk; output_path points into a directory the user lacks write permission for; cross-device rename when part_path and output_path resolve onto different mount points.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


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

Appendix: source

Thrown at src-tauri/omniget-core/src/core/http_fetcher.rs:261

        ensure_part_file(&part_path, total).await?;

        let result = self
            .download_chunked(
                &part_path,
                total,
                segments,
                &resume_path,
                &url_hash,
                &progress_tx,
            )
            .await;

        match result {
            Ok(()) => {
                tokio::fs::rename(&part_path, &self.output_path)
                    .await
                    .map_err(|e| anyhow!("rename .part to final failed: {}", e))?;
                if self.config.use_sidecar_resume {
                    let _ = tokio::fs::remove_file(&resume_path).await;
                }
                let _ = progress_tx.send(ProgressUpdate::percent(100.0)).await;
                let bytes = tokio::fs::metadata(&self.output_path)
                    .await
                    .map(|m| m.len())
                    .unwrap_or(total);
                Ok(HttpFetcherResult {
                    bytes_written: bytes,
                })
            }
            Err(e) => Err(e),
        }
    }

    async fn probe(&self) -> anyhow::Result<ProbeResult> {
        let remote = probe_remote(

View on GitHub (pinned to 8600b91f42)