Troubleshooting GitHub Package NuGet feeds with GitHub Actions

If you’re building .NET applications with GitHub Actions, then you want to ensure the correct .NET SDK is in place. If you’re using GitHub Packages to access a private NuGet feed then you’ll also need to be able to authenticate to that feed. The actions/setup-dotnet action is great for doing both of these tasks, but there are some gotchas I’ve encountered when using it.

Using the action is pretty straight forward:

- uses: actions/setup-dotnet@v6

If you have a global.json file present in the root of your repo it will read it by default. If you don’t then it doesn’t know which version to install and you’ll see this logged in the workflow run:

The global.json wasn't found in the root directory. No .NET version will be installed.

Better to use dotnet-version to indicate one (or more) SDK version ranges to install:

- uses: actions/setup-dotnet@v6
  with:
    dotnet-version: 10.0.x

That is fine, but the best approach in my opinion is to have a global.json file with an sdk property set like this:

{
  "sdk": {
    "version": "10.0.401",
    "rollForward": "latestFeature"
  },
  "test": {
    "runner": "Microsoft.Testing.Platform"
  }
}

The action will read the SDK version from there. This is often a good thing as it means you just need to set the version in one place rather than all through your workflow files.

Using GitHub Packages feeds

Assuming you’ve already configured your GitHub packages feed in your NuGet.Config file (and note the use of the newer packageSourceMapping block):

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <packageSources>
        <add key="nuget" value="https://api.nuget.org/v3/index.json" />
        <add key="github" value="https://nuget.pkg.github.com/flcdrg/index.json" />
    </packageSources>

    <packageSourceMapping>
        <packageSource key="nuget">
            <package pattern="*" />
        </packageSource>
        <packageSource key="github">
            <package pattern="Verify.MongoDB" />
        </packageSource>
    </packageSourceMapping>
</configuration>

Then you can use the action to wire up authentication for the Packages feed with the source-url input parameter:

- uses: actions/setup-dotnet@v6
  with:
    source-url: https://nuget.pkg.github.com/flcdrg/index.json
  env:
    NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}

Note that you must grant the GitHub token access to read packages!

permissions:
  contents: read
  packages: read

And you’d think that would be it. But there are some edge cases to be aware of.

Building from a subdirectory

The action defaults to looking in the root of the repository for global.json and NuGet.Config files. If your .NET source code resides in a subdirectory (often the case if you are using a monorepo approach), then make sure you tell the action where they are:

- uses: actions/setup-dotnet@v6
  with:
    global-json-file: globaljson-and-nugetconfig/global.json
    source-url: https://nuget.pkg.github.com/flcdrg/index.json
    config-file: globaljson-and-nugetconfig/nuget.config

If you forget to set global-json-file and/or config-file then it will only look in the root of the repository, and either load the wrong files or load nothing if those files don’t exist at the root (as we saw earlier).

When the action/setup-dotnet action runs with source-url, it actually generates an additional temporary nuget.config file. You can see this in the workflow run output:

dotnet-auth: Finding any source references in /home/runner/work/using-setup-dotnet-action/using-setup-dotnet-action/nuget.config, writing a new temporary configuration file with credentials to /home/runner/work/using-setup-dotnet-action/nuget.config

That temporary file contains the authentication details for the feed.

But the restore fails:

Determining projects to restore...
/usr/share/dotnet/sdk/10.0.401/NuGet.targets(198,5): warning : Your request could not be authenticated by the GitHub Packages service. Please ensure your access token is valid and has the appropriate scopes configured. [/home/runner/work/using-setup-dotnet-action/using-setup-dotnet-action/globaljson-and-nugetconfig/tests/tests.csproj]
  Retrying 'FindPackagesByIdAsync' for source 'https://nuget.pkg.github.com/flcdrg/download/verify.mongodb/index.json'.

If you take a look at the contents of the temporary nuget.config file you might see the problem:

<configuration>
  <config>
    <add key="defaultPushSource" value="https://nuget.pkg.github.com/flcdrg/index.json"/>
  </config>
  <packageSources>
    <add key="Source" value="https://nuget.pkg.github.com/flcdrg/index.json"/>
  </packageSources>
  <packageSourceCredentials>
    <Source>
      <add key="Username" value="flcdrg"/>
      <add key="ClearTextPassword" value="***"/>
    </Source>
  </packageSourceCredentials>
</configuration>

The action knows the feed URL, but it has defaulted to using Source as the source name.

The issue here is that when dotnet restore runs, it finds the nuget.config file in the application directory and combines that with the temporary file.

The packageSourceMapping defines the github source as the only place to look for Verify.MongoDB package. But the temporary nuget.config file didn’t use that name. So you end up that error.

The solution is to set the config-file input parameter so that the action can use the correct source name from your nuget.config file. With the names aligned, everything should work correctly.

Summary

  • If global.json is not in the root of the repository, use global-json-file
  • If NuGet.Config is not in the root of the repository, use config-file

SA Power Networks meter data

Seven-day Home Assistant statistics graph showing electricity imported from and exported to the grid

For folks living in South Australia, electricity is distributed by SA Power Networks. As an electricity consumer, you don’t normally deal with them directly. Instead you go through a retail company, of which there are a number to choose from.

SA Power Networks provide a service that allows you as a consumer to download your electricity data - Your Meter Data (you register for the service here).

Once you sign in you either download a PDF summary or a CSV file (in ‘NEM12’ format) with all your meter data, which includes both incoming and outgoing (like excess solar or battery) amounts.

I was keen to get that data so I could do some analysis of our power usage.

Having to repeatedly sign into that page and click on the various options just to download a CSV is a bit annoying. Ideally they’d provide a customer-facing API that I could call, but they don’t. (I presume they do provide an API for the electricity retailers).

So I wrote a .NET library and an example console application that will do all that for you from the command line. Source available at https://github.com/flcdrg/sapowernetworks-meterdata-net.

You just supply your email address and password (that you signed up to the Your Meter Data site with) and it will grab the CSV for you:

dotnet run --project src/Gardiner.SaPowerNetworks.MeterData.Console -- --email your@email.com --password 'YOURPASSWORD' --file data.csv

If all goes well, will produce:

Logging in as your@email.com...
Logged in successfully.
Found 1 NMI(s):
  200012345678  Home  STREET SUBURBNAME 5000
Fetching data from 2026-08-18 to 2026-08-20...
Saved detailed NEM12 CSV to /Users/david/dev/sapowernetworks-meterdata-net/data.csv

The data looks something like this (truncated):

200,1234567899,E1B1,E1,E1,,LG1201234567,KWH,05,
300,20260619,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,
400,1,154,F19,,
400,155,288,A,,

And what about Home Assistant?

Hey, wouldn’t this be a great data source for integrating with Home Assistant? You could use it to import your electricity meter data directly into HA. Well yes it would, but as nice as my console app is, it’s written in .NET and Home Assistant integrations are written in Python.

So I used Copilot to create a new Home Assistant integration that imports the data each day automatically! It was able to use the .NET implementation as a reference and created equivalent Python code that would work with Home Assistant.

The source for the integration is at https://github.com/flcdrg/sapowernetworks-meterdata-ha. For the moment you will need to add that as a custom repository in Home Assistant HACS, but I’ve submitted it to be listed so that you will be able to just search for it in the HACS store.

The integration provides a control and a number of sensors:

Screenshot of Integration in Home Assistant

But because all the data is historic, it doesn’t provide a sensor with the current reading. Instead it publishes the historic data to Home Assistant’s Recorder as statistics.

One way to view the data is via a Statistics graph card.

eg.

Screenshot of Statistics Graph settings in Home Assistant

- type: statistics-graph
  entities:
    - sapowernetworks:9999999_import_e1
    - sapowernetworks:9999999_export_b1
  days_to_show: 7
  period: day
  chart_type: bar
  stat_types:
    - change
  logarithmic_scale: false

Which can give you a graph like this:

Seven-day Home Assistant statistics graph showing electricity imported from and exported to the grid

The blue ‘import’ is the electricity that came from the grid into the house. The yellow ‘export’ is excess solar power that was exported back to the grid.

The Saturday 19th and Sunday 20th were both beautiful sunny days here in Adelaide. The reason for the differences in the amounts on those days is that Saturday I was out driving most of the day, so we did not charge our car during the 11am-2pm free period that our current electricity plan offers. But Sunday once I got home from church the car was plugged in and recharged, so for that period while it was just as sunny, that solar power was going straight into our car instead of being exported.

More recent posts

Older posts

All posts (currently 1191) can be found in the archive. Jump to recent years in 2026 , 2025 and 2024 .