Skip to content

Fix dotnet tool exec / dnx failing when tool exists in dotnet-tools.json but not yet restored#53361

Merged
marcpopMSFT merged 2 commits intorelease/10.0.3xxfrom
copilot/fix-dotnet-tool-exec-bug
Mar 13, 2026
Merged

Fix dotnet tool exec / dnx failing when tool exists in dotnet-tools.json but not yet restored#53361
marcpopMSFT merged 2 commits intorelease/10.0.3xxfrom
copilot/fix-dotnet-tool-exec-bug

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 10, 2026

dotnet tool exec and dnx fail with "Run 'dotnet tool restore' to make the command available" if the tool appears in a local dotnet-tools.json manifest — even though these commands are supposed to handle downloading and running a tool without any prior setup.

Root cause

In ToolExecuteCommand.Execute(), the manifest-based path correctly calls ToolPackageRestorer.InstallPackage(), which downloads the package and returns a SaveToCache tuple. However, localToolsResolverCache.Save() was never called with that result. The subsequent LocalToolsCommandResolver then fails to find the tool in the (empty) cache and throws the error.

Fix

ToolExecuteCommand.Execute() — save the restore result to the cache before invoking the resolver:

if (restoreResult.SaveToCache is not null)
{
    localToolsResolverCache.Save(new Dictionary<RestoredCommandIdentifier, ToolCommand>
    {
        { restoreResult.SaveToCache.Value.restoredCommandIdentifier, restoreResult.SaveToCache.Value.toolCommand }
    });
}

Note: SaveToCache is null when PackageHasBeenRestored returns true (tool already in the on-disk cache), so this only applies on first download — exactly the broken scenario.

EndToEndToolTests — added regression test ToolExecSucceedsWhenToolIsInLocalManifestButNotRestored covering both exec and dnx, which creates a dotnet-tools.json referencing the tool and runs the command without a prior dotnet tool restore.

Original prompt

This section details on the original issue you should resolve

<issue_title>dotnet tool exec and dnx don't work if dotnet-tools.json happens to reference the same tool</issue_title>
<issue_description>### Describe the bug

I look to dotnet tool exec or dnx as the reliable way to run a .NET CLI tool without worrying about installing it first.
But it turns out it fails if the tool to be invoked happens to be in a dotnet-tools.json file for which dotnet tool restore has not yet run.

To Reproduce

This works fine in isolation:

dotnet tool exec nbgv -y --verbosity quiet -- get-version -h

But if .config/dotnet-tools.json exists with the following content:

{
  "version": 1,
  "isRoot": true,
  "tools": {
    "nbgv": {
      "version": "3.9.50",
      "commands": [
        "nbgv"
      ],
      "rollForward": false
    }
  }
}

Then the same command will fail with:

Run "dotnet tool restore" to make the "nbgv" command available.

This makes it difficult to run a command from a script that is designed to run anywhere, whether it be .NET 8, 10,

dnx fails with the same error.

Further technical details

details of dotnet --info

.NET SDK: Version: 10.0.103 Commit: c2435c3e0f Workload version: 10.0.100-manifests.a62d7899 MSBuild version: 18.0.11+c2435c3e0

Runtime Environment:
OS Name: ubuntu
OS Version: 24.04
OS Platform: Linux
RID: linux-x64
Base Path: /usr/share/dotnet/sdk/10.0.103/

.NET workloads installed:
There are no installed workloads to display.
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: 10.0.3
Architecture: x64
Commit: c2435c3e0f

.NET SDKs installed:
10.0.103 [/usr/share/dotnet/sdk]

.NET runtimes installed:
Microsoft.AspNetCore.App 10.0.3 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 10.0.3 [/usr/share/dotnet/shared/Microsoft.NETCore.App]

Other architectures found:
None

Environment variables:
DOTNET_GENERATE_ASPNET_CERTIFICATE [false]
DOTNET_NOLOGO [true]
DOTNET_ROLL_FORWARD [Major]
DOTNET_RUNNING_IN_CONTAINER [true]
DOTNET_SDK_VERSION [10.0.103]
DOTNET_USE_POLLING_FILE_WATCHER [true]
DOTNET_VERSION [10.0.3]

global.json file:
Not found

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

Download .NET:
https://aka.ms/dotnet/download

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.

…t not restored

Co-authored-by: marcpopMSFT <12663534+marcpopMSFT@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix dotnet tool exec failure with dotnet-tools.json Fix dotnet tool exec / dnx failing when tool exists in dotnet-tools.json but not yet restored Mar 10, 2026
@marcpopMSFT marcpopMSFT marked this pull request as ready for review March 10, 2026 22:12
Copilot AI review requested due to automatic review settings March 10, 2026 22:12
@marcpopMSFT
Copy link
Copy Markdown
Member

Confirmed that it didn't work before and now works with this fix.

Copy link
Copy Markdown
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 the CLI’s “one-shot” tool execution flow (dotnet tool exec / dnx) where the presence of a local tool manifest (.config/dotnet-tools.json) could cause execution to fail if the tool hadn’t been restored yet. The change ensures the manifest-based restore path persists restore results to the local tools resolver cache before resolving/running the tool.

Changes:

  • Persist ToolPackageRestorer.InstallPackage()’s SaveToCache result into LocalToolsResolverCache in ToolExecuteCommand.Execute().
  • Add an end-to-end regression test covering both dotnet tool exec and dnx when the tool is listed in a local manifest but not yet restored.

Reviewed changes

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

File Description
src/Cli/dotnet/Commands/Tool/Execute/ToolExecuteCommand.cs Saves newly restored local-tool command info into the resolver cache so resolution succeeds immediately after download.
test/Microsoft.DotNet.PackageInstall.Tests/EndToEndToolTests.cs Adds regression coverage for the manifest-present-but-not-restored scenario for both exec and dnx.

@marcpopMSFT marcpopMSFT enabled auto-merge March 13, 2026 19:15
@marcpopMSFT marcpopMSFT merged commit 1179c9b into release/10.0.3xx Mar 13, 2026
28 checks passed
@marcpopMSFT marcpopMSFT deleted the copilot/fix-dotnet-tool-exec-bug branch March 13, 2026 20:26
@baronfel
Copy link
Copy Markdown
Member

baronfel commented Apr 1, 2026

/backport to release/10.0.2xx

@baronfel
Copy link
Copy Markdown
Member

baronfel commented Apr 1, 2026

/backport to release/10.0.1xx

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Started backporting to release/10.0.2xx (link to workflow run)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Started backporting to release/10.0.1xx (link to workflow run)

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.

5 participants