Devolutions/UniGetUI · error · FileNotFoundException

The completed updater partial file was not found.

Error message

The completed updater partial file was not found.

What it means

PromotePartialDownload moves the completed .part file to its final destination path via File.Move. Before moving, it checks File.Exists on the partial path. If the partial file is absent — deleted, moved by another process, never created, or the destinationPath was mangled so GetPartialPath resolves elsewhere — it throws FileNotFoundException with the partial path as the fileName argument. This is a hard precondition failure: the engine cannot promote a download that does not exist on disk.

Source

Thrown at src/UniGetUI.Core.Tools/UpdaterDownloadEngine.cs:278

        newMetadata.TotalLength ??= bytesInPartial;
        newMetadata.UpdatedUtc = DateTime.UtcNow;
        WriteMetadata(metadataPath, newMetadata, log);

        return new UpdaterDownloadResult(
            partialPath,
            bytesInPartial,
            newMetadata.TotalLength,
            response.StatusCode,
            Resumed: appendToPartial
        );
    }

    public static void PromotePartialDownload(string destinationPath, Action<string>? log = null)
    {
        string partialPath = GetPartialPath(destinationPath);
        if (!File.Exists(partialPath))
        {
            throw new FileNotFoundException("The completed updater partial file was not found.", partialPath);
        }

        File.Move(partialPath, destinationPath, overwrite: true);
        DeleteFileIfExists(GetPartialMetadataPath(destinationPath), log);
    }

    public static void DeletePartialDownload(string destinationPath, Action<string>? log = null)
    {
        DeleteFileIfExists(GetPartialPath(destinationPath), log);
        DeleteFileIfExists(GetPartialMetadataPath(destinationPath), log);
    }

    public static bool IsFailureBackoffActive(
        string statePath,
        UpdaterDownloadIdentity identity,
        DateTime utcNow,
        out TimeSpan remaining,
        out UpdaterDownloadFailureState? state,

View on GitHub (pinned to 9b1d7d0eab)

Solutions

  1. Verify the download completed successfully (UpdaterDownloadResult) before calling PromotePartialDownload.
  2. Ensure the exact same destinationPath string is used for both DownloadInstallerPartAsync and PromotePartialDownload.
  3. Check for concurrent cleanup paths or antivirus interference that removes .part files.
  4. Call File.Exists on GetPartialPath(destinationPath) before promoting to give a clearer error or recover gracefully.

Example fix

// before: promote called without confirming the partial exists
UpdaterDownloadEngine.PromotePartialDownload(destPath);
// after: guard the promotion
string partial = UpdaterDownloadEngine.GetPartialPath(destPath);
if (File.Exists(partial))
    UpdaterDownloadEngine.PromotePartialDownload(destPath);
else
    await UpdaterDownloadEngine.DownloadInstallerPartAsync(client, identity, destPath);
Defensive patterns

Strategy: validation

Validate before calling

string partial = UpdaterDownloadEngine.GetPartialPath(destPath);
if (!File.Exists(partial))
    throw new InvalidOperationException($"Cannot promote: partial file missing at {partial}");

Type guard

static bool CanPromotePartial(string destPath) => File.Exists(UpdaterDownloadEngine.GetPartialPath(destPath));

Try / catch

try { UpdaterDownloadEngine.PromotePartialDownload(destPath); }
catch (FileNotFoundException ex) { Logger.Error(ex); /* re-download before promoting */ }

Prevention

When it happens

Trigger: PromotePartialDownload is called but the corresponding .part file does not exist at GetPartialPath(destinationPath). This happens when DeletePartialDownload was already called (e.g. by a cleanup path or an error handler), when the download never ran, or when another process/thread removed the partial file between download completion and promotion.

Common situations: A race condition: one thread calls DeletePartialDownload while another calls PromotePartialDownload. An antivirus or cleanup utility quarantined the .part file. The destinationPath passed to PromotePartialDownload differs from the one passed to DownloadInstallerPartAsync (e.g. different working directory), so GetPartialPath resolves to a different location. The download was interrupted and PromotePartialDownload was invoked without a successful DownloadInstallerPartAsync call first.

Related errors


AI-assisted analysis of Devolutions/UniGetUI@9b1d7d0eab (2026-08-13). Data as JSON: /api/errors/665116a135e9eb55. Report an issue: GitHub.