Skip to content

Avoid NullReferenceException when requested package version is not on the source#25

Open
droyad wants to merge 1 commit into
release/7.3.xfrom
robw/fix-nre-missing-package-version-downloader
Open

Avoid NullReferenceException when requested package version is not on the source#25
droyad wants to merge 1 commit into
release/7.3.xfrom
robw/fix-nre-missing-package-version-downloader

Conversation

@droyad

@droyad droyad commented Jun 5, 2026

Copy link
Copy Markdown

Bug

Fixes: n/a — surfaced in OctopusDeploy/Calamari (FD-440). The defect is upstream NuGet code, unchanged since 2017 and still present on upstream's default branch; no open NuGet/Home issue.

Description

SourceRepositoryDependencyProvider.GetPackageDownloaderAsync gets a downloader from the inner FindPackageByIdResource and then unconditionally calls SetThrottle/SetExceptionHandler on it:

var packageDownloader = await _findPackagesByIdResource.GetPackageDownloaderAsync(...);
packageDownloader.SetThrottle(_throttle);            // NRE when packageDownloader is null
packageDownloader.SetExceptionHandler(...);

The inner method is declared Task<IPackageDownloader?> and returns null by contract when the requested version doesn't exist on the source (e.g. HttpFileSystemBasedFindPackageByIdResource returns null when the version isn't in the flat-container list). So requesting a downloader for a missing version throws a bare NullReferenceException instead of surfacing "not found".

Why it's gone unnoticed: restore never reaches this with a missing version, because dependency resolution (FindLibraryAsync) validates the version before the download step. Callers that request a downloader for a specific version directly — e.g. Octopus Calamari's V3 package download — hit the unprotected path.

Fix: null-check the downloader and propagate the null, honouring the nullable contract of the resource rather than dereferencing it. No behaviour change for the resolve-then-download path; the previously-NRE case now returns null for the caller to handle.

Targeting / CI: targets release/7.4.x. That branch's GitHub Actions build is currently broken (missing the SDK-resolution fix that only landed on 7.3.x), so #26 ports that fix to 7.4.x and should merge first for this PR to get a green run.

No test added — this is a one-line defensive guard on a code path upstream exercises only indirectly; behavioural coverage lives downstream in Calamari (GivesActionableErrorWhenV3FeedIsMissingTheRequestedVersion).

PR Checklist

  • Meaningful title, helpful description and a linked NuGet/Home issue
  • Added tests
  • Link to an issue or pull request to update docs if this PR changes settings, environment variables, new feature, etc.

// exist on the source. Propagate that null instead of dereferencing it below
// (SetThrottle/SetExceptionHandler), which otherwise throws a bare NullReferenceException for a
// version that isn't on the feed.
return null;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Code below returns null as well, so it's a known return value.

Comment on lines +478 to +481
// FindPackageByIdResource returns a null downloader when the requested package version does not
// exist on the source. Propagate that null instead of dereferencing it below
// (SetThrottle/SetExceptionHandler), which otherwise throws a bare NullReferenceException for a
// version that isn't on the feed.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Suggested change
// FindPackageByIdResource returns a null downloader when the requested package version does not
// exist on the source. Propagate that null instead of dereferencing it below
// (SetThrottle/SetExceptionHandler), which otherwise throws a bare NullReferenceException for a
// version that isn't on the feed.

@droyad droyad changed the base branch from release/7.3.x to release/7.4.x June 5, 2026 05:21
@droyad droyad force-pushed the robw/fix-nre-missing-package-version-downloader branch from e05fadc to 38cb960 Compare June 5, 2026 05:27
@droyad droyad changed the base branch from release/7.4.x to release/7.3.x June 5, 2026 05:43
SourceRepositoryDependencyProvider.GetPackageDownloaderAsync retrieves a
downloader from the inner FindPackageByIdResource and then unconditionally
calls SetThrottle/SetExceptionHandler on it. FindPackageByIdResource
returns a null downloader when the requested package version does not
exist on the source, so for a missing version this threw a bare
NullReferenceException.

Restore never hits this because dependency resolution validates the
version before downloading, but callers that request a downloader for a
specific version directly (e.g. Calamari's V3 package download) do. Null-
check the downloader and propagate the null, matching the not-found
contract of FindPackageByIdResource.GetPackageDownloaderAsync.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@droyad droyad force-pushed the robw/fix-nre-missing-package-version-downloader branch from 38cb960 to f21310c Compare June 5, 2026 05:44
@droyad

droyad commented Jun 5, 2026

Copy link
Copy Markdown
Author

Closing in favour of OctopusDeploy/Calamari#1994

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.

1 participant