Skip to content

Fix opaque NullReferenceException when a NuGet V3 package version is missing (FD-440)#1994

Open
droyad wants to merge 1 commit into
mainfrom
robw/fd-440-nuget-v3-missing-version-direct
Open

Fix opaque NullReferenceException when a NuGet V3 package version is missing (FD-440)#1994
droyad wants to merge 1 commit into
mainfrom
robw/fd-440-nuget-v3-missing-version-direct

Conversation

@droyad

@droyad droyad commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Background

Deploying with a package set to download on the target, from a NuGet V3 feed, for a version that doesn't exist on the feed, failed with an opaque NullReferenceException — no hint that the real problem is a missing/unpublished version:

System.NullReferenceException: Object reference not set to an instance of an object.
   at NuGet.Commands.SourceRepositoryDependencyProvider.GetPackageDownloaderAsync(...)
   at Calamari.Integration.Packages.NuGet.NuGetV3LibDownloader.DownloadPackage(...)
   ...
   at Calamari.Commands.DownloadPackageCommand.Execute(...)

Customer-facing context and the end-to-end product repro are in FD-440.

The problem is described in this PR for NuGet.Client. But since we don't want to add more things ot it, and the likelyhood of this getting fixed upstream in a timely manner are low, this PR avoids the problem.

Verification

Reproduced and fixed end-to-end on a local Octopus Server: a Run a Script step running on a worker pool, referencing Newtonsoft.Json version 9999.9.9 (which doesn't exist) from a NuGet V3 feed (https://api.nuget.org/v3/index.json), with the package acquired on the worker.

Before (stock Calamari) — bare NRE wrapped by the retry message:

Error | The package Newtonsoft.Json version 9999.9.9 could not be downloaded from the external feed
        'https://api.nuget.org/v3/index.json' after making 5 attempts over 101s...
Error | System.NullReferenceException: Object reference not set to an instance of an object.
Error |   at NuGet.Commands.SourceRepositoryDependencyProvider.GetPackageDownloaderAsync(...)
Error |   at Calamari...NuGetV3LibDownloader.DownloadPackage(...) NuGetV3LibDownloader.cs:line 39

After (this change) — clear, actionable message, no NullReferenceException anywhere in the log:

Verbose | Attempt 1 of 5: Package Newtonsoft.Json version 9999.9.9 was not found on the NuGet feed
          'https://api.nuget.org/v3/index.json'. Make sure the package version has been pushed to the feed.

Automated coverage: integration test GivesActionableErrorWhenV3FeedIsMissingTheRequestedVersion downloads a missing version from api.nuget.org — fails with the exact NRE above without this change, passes with it.

On the retries: the download still retries all attempts before the deployment fails — left as-is intentionally. A version that's absent right now may legitimately be published to the feed mid-window (e.g. a lagging CI push), so retrying is the desired behaviour. This change only improves the message; it doesn't alter the retry policy.

Fixes FD-440

…-440)

When a requested package version doesn't exist on a NuGet V3 feed, the
download failed with an opaque NullReferenceException thrown inside the
(forked) NuGet.Commands SourceRepositoryDependencyProvider, which
dereferences a null downloader returned for the absent version.

Instead of going through that provider, NuGetV3LibDownloader now resolves
the downloader from FindPackageByIdResource directly and null-checks it,
throwing an actionable "version not found" error. This removes the buggy
provider wrapper from the download path entirely; its throttle and
ignore-failed-sources behaviour is irrelevant here, as Calamari passes no
throttle and downloads from a single source.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Comment on lines +42 to +51
var findPackageByIdResource = sourceRepository.GetResourceAsync<FindPackageByIdResource>(CancellationToken.None)
.GetAwaiter()
.GetResult();

var packageIdentity = new PackageIdentity(packageId, version.ToNuGetVersion());

string targetTempNupkg = Path.Combine(targetPath, Path.GetRandomFileName());
var packageDownloader = providers.GetPackageDownloaderAsync(new PackageIdentity(packageId, version.ToNuGetVersion()), sourceCacheContext, logger, CancellationToken.None)
.GetAwaiter()
.GetResult();
var packageDownloader = findPackageByIdResource.GetPackageDownloaderAsync(packageIdentity, sourceCacheContext, logger, CancellationToken.None)
.GetAwaiter()
.GetResult();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change is essentially pulling the core of the method shown in this PR inline and adding the null check here - this assumes the linked PR doesn't get merged.

@droyad droyad changed the title Resolve NuGet V3 downloader from FindPackageByIdResource directly (FD-440, alt to #1989) Fix opaque NullReferenceException when a NuGet V3 package version is missing (FD-440) Jun 5, 2026
var packageDownloader = providers.GetPackageDownloaderAsync(new PackageIdentity(packageId, version.ToNuGetVersion()), sourceCacheContext, logger, CancellationToken.None)
.GetAwaiter()
.GetResult();
var packageDownloader = findPackageByIdResource.GetPackageDownloaderAsync(packageIdentity, sourceCacheContext, logger, CancellationToken.None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed sourceRepository.GetResourceAsync can return null, so we might need null handling for findPackageByIdResource as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants