Skip to content

PackAsTool: Copilot CLI binary not included in NuGet tool package (cross-platform) #2067

Description

@Marcus-Kanon

Description

When packaging a .NET application as a dotnet tool (PackAsTool=true) that depends on GitHub.Copilot.SDK, the native Copilot CLI binary is not included in the .nupkg tool layout. At runtime, the tool fails with:

Copilot runtime not found at '...tools/net10.0/win-x64/runtimes/win-x64/native/copilot.exe'.
Ensure the SDK NuGet package was restored correctly or provide an explicit RuntimeConnection.ForStdio(path: ...) / RuntimeConnection.ForTcp(path: ...).

Root Cause

The SDK's build targets use ContentWithTargetPath with CopyToOutputDirectory to copy the CLI binary to bin/runtimes/{rid}/native/. However, PackAsTool does not include ContentWithTargetPath items in the tool package layout. The binary ends up in the build output but not in the .nupkg.

Additionally, when building on a Linux CI agent, the SDK downloads only the Linux binary. Even with RuntimeIdentifiers=win-x64;linux-x64, the per-RID builds download the correct platform binary, but it still doesn't make it into the tool package.

Steps to Reproduce

  1. Create a project with PackAsTool=true and GitHub.Copilot.SDK reference
  2. Run dotnet pack on a Linux agent
  3. Install the tool on Windows via dotnet tool install or dotnet dpx
  4. Run the tool -> crash with "Copilot runtime not found"

Expected Behavior

The Copilot CLI binaries for all target RIDs should be included in the NuGet tool package, similar to how packages like libgit2sharp and SkiaSharp ship native binaries under runtimes/{rid}/native/ in the package itself.

Suggested Fix

Ship the Copilot CLI binaries as proper runtimes/{rid}/native/ assets in the GitHub.Copilot.SDK NuGet package (all supported platforms), instead of downloading them at build time. This would make PackAsTool, PublishSingleFile, and cross-platform CI packaging all work correctly.

Environment

  • GitHub.Copilot.SDK: 1.0.8-preview.0
  • .NET SDK: 10.0.302
  • OS: Linux (CI build agent) / Windows (runtime)

Activity

  1. github-actions commented on Jul 23, 2026

    @github-actions
    Contributor

    Investigation

    I investigated this issue by examining the .NET SDK build targets at dotnet/src/build/GitHub.Copilot.SDK.targets.

    What the code does

    The _RegisterCopilotCliForCopy target (line 146–163) registers the Copilot CLI binary as a ContentWithTargetPath item with CopyToOutputDirectory="PreserveNewest". This works correctly for regular dotnet build and dotnet publish scenarios — the binary is copied to bin/runtimes/{rid}/native/ at build time.

    The bug

    However, ContentWithTargetPath items are not included in the .nupkg tool layout when PackAsTool=true. The NuGet pack process for dotnet tools only packages the build output, not ContentWithTargetPath items that are dynamically injected via a .targets file in the consuming project. The CLI binary ends up in the build output directory but is never written into the .nupkg, so when the tool is installed on another machine (especially a different OS/RID) the binary is simply absent.

    A secondary issue noted by the reporter is also real: the build targets download only the CLI binary for the current build host RID — even if RuntimeIdentifiers lists multiple RIDs. Cross-platform tool packaging (e.g., building on Linux for a tool that should run on Windows) is therefore not supported by the current design.

    Root cause

    The SDK was designed to download and copy the CLI binary at build time into the output directory. This approach works for applications but breaks for dotnet tool packages (PackAsTool=true) and cross-platform publishing scenarios, because:

    1. ContentWithTargetPath/CopyToOutputDirectory items are not included in the NuGet tool package layout.
    2. Only the host-platform binary is downloaded, so multi-RID cross-compilation is not supported.

    Classification

    This is a bug. The SDK does not document any limitation around PackAsTool, and the current implementation silently produces a broken tool package. The fix would require either shipping the CLI binaries as proper runtimes/{rid}/native/ NuGet package assets (as the reporter suggests, similar to libgit2sharp/SkiaSharp), or adding explicit support for the PackAsTool path in the build targets.

    Warning

    Firewall blocked 1 domain

    The following domain was blocked by the firewall during workflow execution:

    • awmgmcpg

    To allow these domains, add them to the network.allowed list in your workflow frontmatter:

    network:
      allowed:
        - defaults
        - "awmgmcpg"

    See Network Configuration for more information.

    Generated by Bug Handler for #2067 · 19.8 AIC · ⌖ 5 AIC · ⊞ 4.5K · ◷

  2. kondv commented on Sep 8, 2026

    @kondv
    Contributor

    Root cause is narrower than the runtime-asset redesign suggested above: tool packages are built from the publish layout, but the SDK registered its CLI runtime assets only for build output, so packing without a rebuild dropped them. #2557 registers the same assets for publish as well. Verified end to end with a locally built SDK consumed by an external PackAsTool project outside the repo — unpatched main produced a package with zero runtimes/*/native entries and the installed tool crashed with Copilot runtime wrapper not found, while the patched build packaged 67 native entries and the installed tool started the real CLI and completed an RPC call. pack -r <rid> and multi-RID packing already worked before the change, so the gap is specific to packing without rebuilding.

    — kondv's Copilot 🤖

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions