Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Feb 10, 2026

Fix dotnet tool install NullReferenceException Regression

Summary

Fixes a critical regression in .NET 11.0.100-preview.2 where dotnet tool install would fail with "Unhandled exception: Object reference not set to an instance of an object."

Solution

Modified the shared ExplicitManifestOrFindManifestContainPackageId extension method to accept an optional throwIfNoManifestFound parameter:

  • For install: Pass false to allow manifest creation when none exists
  • For uninstall: Default true maintains existing behavior (throws if no manifest found)

This approach avoids code duplication and centralizes the manifest lookup logic in the shared method.

Changes

  • ToolManifestFinderExtensions.cs:
    • Added throwIfNoManifestFound parameter (defaults to true)
    • Removed #nullable disable and properly annotated nullable types
    • Changed return type to (FilePath? filePath, string? warningMessage)
    • Changed parameter to string? explicitManifestFile
  • ToolInstallLocalCommand.cs:
    • Simplified to use shared method with throwIfNoManifestFound: false
    • Updated to use string? for warningMessage
  • ToolUninstallLocalCommand.cs: No changes needed (uses default true behavior)
  • ToolInstallLocalCommandTests.cs: Added explicit regression test

Testing

  • ✅ All 21 unit tests pass (20 existing + 1 new regression test)
  • ✅ All 9 uninstall tests pass
  • ✅ New test GivenExistingEmptyManifestItShouldInstallToolSuccessfully specifically covers the regression
  • ✅ Manually verified exact repro scenario works
  • ✅ Verified uninstall still throws when no manifest exists
  • ✅ Proper nullability annotations prevent future NRT warnings

Risk Assessment

Low - Cleaner solution that leverages existing shared code with explicit regression coverage and proper nullability

Original prompt

This section details on the original issue you should resolve

<issue_title>[NETSDKE2E][Regression] With NET11.0 preview 2 SDK, 'dotnet tool' command fails - Unhandled exception: Object reference not set to an instance of an object.</issue_title>
<issue_description>REGRESSION INFO
Works well on 11.0.100-preview.1.26104.118

INSTALL STEPS

  1. Install .NET 11.0.100-preview.2.26108.103 SDK on Windows11

REPRO STEPS

  1. Create empty directory and cd to the directory
  2. Run dotnet new tool-manifest
  3. Run dotnet tool install dotnet-ef

EXPECTED
Run dotnet tool install dotnet-ef successfully.

Image

ACTUAL
Shows Unhandled exception: Object reference not set to an instance of an object.
Image

Dotnet --info
.NET SDK:
Version: 11.0.100-preview.2.26108.103
Commit: 8c56ff58fc
Workload version: 11.0.100-manifests.3e326a28
MSBuild version: 18.5.0-preview-26108-103+8c56ff58f

Runtime Environment:
OS Name: Windows
OS Version: 10.0.26200
OS Platform: Windows
RID: win-x64
Base Path: C:\Program Files\dotnet\sdk\11.0.100-preview.2.26108.103\

.NET workloads installed:
[wasm-tools]
Installation Source: SDK 11.0.100-preview.2
Manifest Version: 11.0.100-preview.2.26108.103/11.0.100-preview.2
Manifest Path: C:\Program Files\dotnet\sdk-manifests\11.0.100-preview.2\microsoft.net.workload.mono.toolchain.current\11.0.100-preview.2.26108.103\WorkloadManifest.json
Install Type: Msi

Configured to use workload sets when installing new manifests.
No workload sets are installed. Run "dotnet workload restore" to install a workload set.

Host:
Version: 11.0.0-preview.2.26108.103
Architecture: x64
Commit: 8c56ff58fc

.NET SDKs installed:
11.0.100-preview.2.26108.103 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
Microsoft.AspNetCore.App 11.0.0-preview.2.26108.103 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 11.0.0-preview.2.26108.103 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 11.0.0-preview.2.26108.103 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
None

Environment variables:
Not set

global.json file:
Not found

Learn more:
https://aka.ms/dotnet/info

Download .NET:
https://aka.ms/dotnet/download</issue_description>

<agent_instructions>Confirmed that this is broken in main but not in preview 1. Please take a look.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits February 10, 2026 19:20
Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>
Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix unhandled exception in dotnet tool command Fix NullReferenceException in tool install when package not found in manifest Feb 10, 2026
Copilot AI requested a review from marcpopMSFT February 10, 2026 19:26
@marcpopMSFT
Copy link
Member

@copilot I see that you modified ToolInstallLocalCommand. Wouldn't it be better to fix the shared method that is ExplicitManifestOrFindManifestContainPackageId to handle this situation rather than duplicating some of that code?

Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 11, 2026

@copilot I see that you modified ToolInstallLocalCommand. Wouldn't it be better to fix the shared method that is ExplicitManifestOrFindManifestContainPackageId to handle this situation rather than dup...

You're absolutely right. I've refactored the fix to modify the shared ExplicitManifestOrFindManifestContainPackageId method instead of duplicating logic.

The method now accepts an optional throwIfNoManifestFound parameter (defaults to true for backward compatibility):

  • Install passes false to gracefully handle missing manifests
  • Uninstall uses the default true to maintain its existing behavior

Commit: 0c4ffbf

@marcpopMSFT marcpopMSFT requested a review from baronfel February 12, 2026 18:40
@marcpopMSFT
Copy link
Member

Before

@marcpopMSFT ➜ /workspaces/test $ dotnet --version
11.0.100-preview.2.26111.109
@marcpopMSFT ➜ /workspaces/test $ dotnet tool install dotnetsay -g
Unhandled exception: Object reference not set to an instance of an object.

After

(dogfood) @marcpopMSFT ➜ /workspaces/test $ dotnet --version
11.0.100-dev
(dogfood) @marcpopMSFT ➜ /workspaces/test $ dotnet tool install dotnetsay -g
<snipped>
Tool 'dotnetsay' (version '3.0.3') was successfully installed.

@marcpopMSFT
Copy link
Member

Same for local install.

@marcpopMSFT marcpopMSFT marked this pull request as ready for review February 12, 2026 18:42
Copilot AI review requested due to automatic review settings February 12, 2026 18:42
@baronfel
Copy link
Member

@copilot do we have any tests covering tool installation that should have detected this? If not, please create one so we don't regress this ability.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a regression in dotnet tool install where missing tool manifest discovery could surface as an unhandled exception, by allowing install to proceed and create/select a manifest when appropriate.

Changes:

  • Added an optional throwIfNoManifestFound parameter to ExplicitManifestOrFindManifestContainPackageId to optionally suppress manifest-not-found exceptions.
  • Updated local tool install to call the helper with throwIfNoManifestFound: false and fall back to GetManifestFilePath() when no manifest is found/selected.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/Cli/dotnet/Commands/Tool/Install/ToolInstallLocalCommand.cs Uses the updated helper with throwIfNoManifestFound: false and simplifies manifest selection/fallback logic.
src/Cli/dotnet/Commands/Tool/Common/ToolManifestFinderExtensions.cs Adds optional behavior to suppress throwing when no manifest exists, enabling install to handle creation/fallback.

Comment on lines 15 to 19
public static (FilePath? filePath, string warningMessage) ExplicitManifestOrFindManifestContainPackageId(
this IToolManifestFinder toolManifestFinder,
string explicitManifestFile,
PackageId packageId)
PackageId packageId,
bool throwIfNoManifestFound = true)
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

warningMessage is declared as non-nullable (string) in the tuple return type, but this method returns null for it in several paths (explicit manifest, no match, and now the no-manifest catch). This makes the nullability contract incorrect for nullable-enabled callers and can lead to missed null checks / NRT warnings. Consider enabling nullable for this file (or at least for this method) and changing the signature to return string? warningMessage (and likewise accept string? explicitManifestFile).

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed the nullability annotations:

  • Removed #nullable disable from ToolManifestFinderExtensions.cs
  • Changed return type to (FilePath? filePath, string? warningMessage)
  • Changed parameter to string? explicitManifestFile
  • Updated calling code in ToolInstallLocalCommand.cs to use string?

All tests pass (21 install + 9 uninstall).

Commit: 4921020

Comment on lines 105 to 110
(FilePath? manifestFileOptional, string warningMessage) =
_toolManifestFinder.ExplicitManifestOrFindManifestContainPackageId(_explicitManifestFile, packageId);
_toolManifestFinder.ExplicitManifestOrFindManifestContainPackageId(_explicitManifestFile, packageId, throwIfNoManifestFound: false);

if (warningMessage != null)
{
_reporter.WriteLine(warningMessage.Yellow());
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

In this nullable-enabled file, warningMessage is deconstructed as non-nullable string, but the called helper can return null for the warning. This forces a redundant warningMessage != null check (and can hide real nullability issues). After adjusting the helper’s signature, consider deconstructing into string? warningMessage here to match the actual behavior.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was already addressed in commit 4921020. Line 105 now uses string? warningMessage to properly match the nullable return type from the helper method.

Co-authored-by: baronfel <573979+baronfel@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 12, 2026

@copilot do we have any tests covering tool installation that should have detected this? If not, please create one so we don't regress this ability.

Yes, we have existing unit tests that cover tool installation scenarios. The test ToolInstallLocalCommandTests.WhenRunWithPackageIdItShouldSaveToCacheAndAddToManifestFile tests installing into an existing empty manifest, which is the regression scenario.

However, I've added a more explicit regression test GivenExistingEmptyManifestItShouldInstallToolSuccessfully that specifically documents this case and includes a comment linking to the issue. All 21 unit tests now pass.

Commit: 14280f6

Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants