Skip to content

DependencyResolver: offline restore destructively removes committed NuGet DLLs (incomplete keep-closure) + misleading 'All packages up to date' #809

Description

@IvanMurzak

Summary

When NuGetPackageRestorer.Restore() runs while package downloads fail (offline / registry unreachable), NuGetPackageInstaller.RemoveUnnecessaryPackages() still executes its removal sweep with an essentially empty InstalledThisSession keep-closure. Result: it deletes committed, previously-valid DLLs from Assets/Plugins/NuGet/ (case 2: "DLLs now provided by Unity ... and package ID not in the closure", NuGetPackageInstaller.cs:293-307) and strips their entries from .nuget-installed.json.

Observed 2026-06-05 in the ai-game-developer-infra Unity-Test-Project testbed (Unity 6000.5.0b10, plugin source at McpPlugin 6.7.0, committed DLLs at 6.5.2/ReflectorNet 5.2.0):

  • All 14 Install() calls failed with [NuGet] Failed to download <pkg>: An error occurred while sending the request (cold Library = empty NuGetCache, no fallback).
  • The sweep then deleted 14 DLLs (Microsoft.Bcl.AsyncInterfaces, MS.Extensions.*.Abstractions, System.Buffers/Memory/Text.Json/...) that the previous committed state depended on.
  • The manifest is left in a state that permanently fails AllPackagesInstalled() -> full restore re-runs on every domain reload (churn), and the deletions get reverted by the first ONLINE restore anyway - so the offline sweep is pure destruction with no steady state.
  • The pass ends with [NuGet] All packages up to date. because anyChanged == false after total failure - misleading when 14/14 downloads errored.

Proposed fix

  1. Guard the sweep: skip RemoveUnnecessaryPackages (or at minimum its case 2) when any configured top-level Install() failed this session - the keep-closure is incomplete, so removal decisions are unsound.
  2. Do not log "All packages up to date." when installs failed; log a clear restore incomplete: N package(s) failed to download warning instead.

Repro

  1. Commit a valid Assets/Plugins/NuGet state for plugin version X.
  2. Bump plugin source to version Y (newer deps).
  3. Delete Library/, block network access to nuget registries, open the project.
  4. Observe: compile errors (expected), then the resolver deletes previously-committed DLLs and reports "All packages up to date."

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions