You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Starting with .NET 11, Environment.GetCommandLineArgs()[0] returns the name the apphost was invoked with instead of the managed .dll path (dotnet/runtime#131671). RootCommand.ExecutableName runs Path.GetFileNameWithoutExtension on that value. On Linux/macOS the apphost has no extension, so any app whose name contains a dot has its last segment treated as an "extension" and dropped. For example, Contoso.Tool becomes Contoso.
This affects the root command's Name, so it changes the Usage: line in help output. It also affects everything else derived from ExecutableName. Apps get this just by retargeting from net10.0 to net11.0, with no code changes.
Background: the .NET 11 runtime change
dotnet/runtime#131671, "Fix Environment.GetCommandLineArgs()[0] to report the host invocation name", merged 2026-08-10 and fixes dotnet/runtime#101837. It adds an ARGV0 host runtime property: hostpolicy captures the apphost's native argv[0], and CoreCLR uses it for element zero of Environment.GetCommandLineArgs(). From that PR:
Command line
Environment.GetCommandLineArgs()
dotnet.exe foo.dll 1 2 3
foo.dll, 1, 2, 3 (unchanged)
foo 1 2 3
foo, 1, 2, 3 (previously the managed …/foo.dll path)
../bar/special_foo.exe 1 2 3 (symlink to foo.exe)
../bar/special_foo.exe, 1, 2, 3
Before this change, args[0] for an apphost launch always ended in .dll, so GetFileNameWithoutExtension happened to strip a real extension on every OS. Now it's the apphost name:
Windows: the apphost name ends in .exe, so stripping the extension is still correct.
Linux/macOS: the apphost has no extension, so a dotted name loses its last segment.
main (RootCommand.cs#L104-L137) has the same core logic: Path.GetFileNameWithoutExtension(path).Replace(" ", "") on L128, where path is Environment.GetCommandLineArgs()[0].
On Windows the same net11.0 app still reports Contoso.Tool, because args[0] is Contoso.Tool.exe. The result is the same app showing different names per OS.
Who is affected
Any System.CommandLine app that:
targets .NET 11 and is launched through its apphost rather than dotnet app.dll, and
runs on Linux or macOS, and
has a dot in its executable name. That's common, because the default AssemblyName is the project name, e.g. Contoso.Tool, MyCompany.Cli, or test projects like Foo.Tests.
Impacts:
Help output:Usage: shows the wrong command name.
Other ExecutableName users: code that uses RootCommand.ExecutableName directly, and features that depend on it, such as completion/dotnet-suggest registration, get the truncated name. A similar issue was fixed earlier for RegisterWithDotnetSuggest in RegisterWithDotnetSuggest should not remove "extension" on Linux #2316.
Tests: help-output snapshot tests become OS-dependent. We hit this in Aspire when retargeting our CLI tests to net11.0: help snapshots started failing only on Linux/macOS, where Aspire.Cli.Tests was rendered as Aspire.Cli (Update Aspire.Cli to target net11.0 microsoft/aspire#20436). Our shipped aspire binary isn't affected only because its name has no dot.
Summary
Starting with .NET 11,
Environment.GetCommandLineArgs()[0]returns the name the apphost was invoked with instead of the managed.dllpath (dotnet/runtime#131671).RootCommand.ExecutableNamerunsPath.GetFileNameWithoutExtensionon that value. On Linux/macOS the apphost has no extension, so any app whose name contains a dot has its last segment treated as an "extension" and dropped. For example,Contoso.ToolbecomesContoso.This affects the root command's
Name, so it changes theUsage:line in help output. It also affects everything else derived fromExecutableName. Apps get this just by retargeting fromnet10.0tonet11.0, with no code changes.Background: the .NET 11 runtime change
dotnet/runtime#131671, "Fix Environment.GetCommandLineArgs()[0] to report the host invocation name", merged 2026-08-10 and fixes dotnet/runtime#101837. It adds an
ARGV0host runtime property: hostpolicy captures the apphost's nativeargv[0], and CoreCLR uses it for element zero ofEnvironment.GetCommandLineArgs(). From that PR:Environment.GetCommandLineArgs()dotnet.exe foo.dll 1 2 3foo.dll,1,2,3(unchanged)foo 1 2 3foo,1,2,3(previously the managed…/foo.dllpath)../bar/special_foo.exe 1 2 3(symlink tofoo.exe)../bar/special_foo.exe,1,2,3Before this change,
args[0]for an apphost launch always ended in.dll, soGetFileNameWithoutExtensionhappened to strip a real extension on every OS. Now it's the apphost name:.exe, so stripping the extension is still correct.The System.CommandLine code involved
2.0.x servicing, e.g. 2.0.12 (dotnet/dotnet
release/10.0.1xx, RootCommand.cs#L48-L54):main(RootCommand.cs#L104-L137) has the same core logic:Path.GetFileNameWithoutExtension(path).Replace(" ", "")on L128, wherepathisEnvironment.GetCommandLineArgs()[0].Repro
Contoso.Tool.csproj:Program.cs:Publish for
linux-x64(I used SDK 11.0.100-rc.1.26425.128), then run./Contoso.Tool --helpon Linux.net10.0
net11.0
On Windows the same net11.0 app still reports
Contoso.Tool, becauseargs[0]isContoso.Tool.exe. The result is the same app showing different names per OS.Who is affected
Any System.CommandLine app that:
dotnet app.dll, andAssemblyNameis the project name, e.g.Contoso.Tool,MyCompany.Cli, or test projects likeFoo.Tests.Impacts:
Usage:shows the wrong command name.ExecutableNameusers: code that usesRootCommand.ExecutableNamedirectly, and features that depend on it, such as completion/dotnet-suggestregistration, get the truncated name. A similar issue was fixed earlier forRegisterWithDotnetSuggestin RegisterWithDotnetSuggest should not remove "extension" on Linux #2316.Aspire.Cli.Testswas rendered asAspire.Cli(Update Aspire.Cli to target net11.0 microsoft/aspire#20436). Our shippedaspirebinary isn't affected only because its name has no dot.cc dotnet/runtime#131671, where I also asked whether the runtime change should be tagged as a breaking change: dotnet/runtime#131671 (comment)