nuget-trusted-publishing
Set up NuGet trusted publishing (OIDC) on a GitHub Actions repo — replaces long-lived API keys with short-lived tokens. USE FOR: trusted publishing, NuGet OIDC, keyless NuGet publish, migrate from NuGet API key, NuGet/login, secure NuGet publishing. DO NOT USE FOR: publishing to
Install
npx skills add https://github.com/dotnet/skills/tree/main/plugins/dotnet-advanced/skills/nuget-trusted-publishing
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install dotnet-skills@llmmart
git clone https://github.com/dotnet/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole dotnet/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
NuGet Trusted Publishing Setup
Set up NuGet trusted publishing on a GitHub Actions repo. Replaces long-lived API keys with OIDC-based short-lived tokens — no secrets to rotate or leak.
Prerequisites
- GitHub Actions — this skill covers GitHub Actions setup only
- nuget.org account — the user needs access to create trusted publishing policies
When to Use This Skill
Use this skill when:
- Setting up trusted publishing for a NuGet package
- Migrating from
secrets.NUGET_API_KEYto OIDC-based publishing - Asked about keyless or secure NuGet publishing
- Creating a new NuGet publish workflow from scratch
- Asked to "remove NuGet API key" or "use NuGet/login"
- Setting up publishing for a dotnet tool, MCP server, or template package
- Asked about
NuGet/login@v1orid-token: write
Safety Rules
⚠️ Bail-out rule: If any phase fails after one fix attempt on an infrastructure/auth issue, stop and ask the user. Don't loop on environment problems.
⚠️ Never delete or overwrite without confirmation: Removing API key secrets, deleting tags/releases, removing workflow steps, or changing package IDs. NuGet package IDs are permanent — mistakes can't be undone.
Process
Fast-path for greenfield repos: When the user has a simple setup (one packable project, no existing publish workflow), don't gate on multi-turn assessment. Combine phases: create the workflow immediately, include nuget.org policy guidance, local pack recommendation, and filename-matching warning all in one response. The full phased process below is for complex or migration scenarios.
Phase 1: Assess
Inspect the repo and report findings before making any changes.
Find and classify packable projects — check
.csprojfiles andDirectory.Build.props(package metadata is often set repo-wide). Classify in this order (earlier matches win):<PackageType>Template</PackageType>→ Template<PackageType>McpServer</PackageType>→ MCP server (also a dotnet tool)<PackAsTool>true</PackAsTool>→ Dotnet tool- Class library (
IsPackable=trueor noOutputType) → Library <OutputType>Exe</OutputType>with<IsPackable>true</IsPackable>→ Application package (not a tool, but still publishable)<OutputType>Exe</OutputType>withoutPackAsToolorIsPackable→ Not packable by default (ask user if they intend to publish it)
Validate structure for each project's type:
Type Required All PackageId,Version(in .csproj or Directory.Build.props)Dotnet tool PackAsTool(required);ToolCommandName(optional but recommended — defaults to assembly name)MCP server PackageType=McpServer,.mcp/server.jsonincluded in packageTemplate PackageType=Template,.template.config/template.jsonunder content dirFind existing publish workflows in
.github/workflows/— look fordotnet nuget push,nuget push, ordotnet pack.Check version consistency — for MCP servers, verify
.csproj<Version>matches bothserver.jsonversion fields (rootversionandpackages[].version). Flag any mismatch.Report findings to the user: classification, missing properties, version mismatches, existing workflows. For multi-project repos, note whether one workflow or separate workflows per package are needed. Offer to fix gaps — use
ask_userbefore modifying project files.
❌ See references/package-types.md for per-type details and required properties.
Phase 2: Local Verification
Pack and verify locally before touching nuget.org — publishing errors waste a permanent version number.
⚠️ Always mention this step, even if you defer running it. Tell the user: "Before your first publish, run
dotnet pack -c Release -o ./artifactsto verify the .nupkg is created correctly."
dotnet pack -c Release -o ./artifacts— verify.nupkgis created- For tools/MCP servers: install from
./artifacts, run--help, uninstall - For libraries: inspect the
.nupkgcontents (it's a zip)
Phase 3: nuget.org Policy
This phase requires the user to act on nuget.org — guide them with exact values.
Determine the repo owner, repo name, and the workflow filename that will publish.
❌ The policy requires the exact workflow filename (e.g.,
publish.ymlorpublish.yaml) — just the filename, no path prefix. Matching is case-insensitive. Don't use the workflowname:field.Guide the user to create the trusted publishing policy:
Go to nuget.org/account/trustedpublishing → Add policy
- Repository Owner:
{owner} - Repository:
{repo} - Workflow File:
{filename}.yml - Environment:
release(only if the workflow usesenvironment:; leave blank otherwise)
Policy ownership: the user chooses individual account or organization. Org-owned policies apply to all packages owned by that org.
For private repos: policy is "temporarily active" for 7 days — becomes permanent after the first successful publish.
- Repository Owner:
Guide the user to create a GitHub Environment (recommended but optional — provides secret scoping + approval gates):
Repo Settings → Environments → New environment →
releaseAdd environment secret: Name =
NUGET_USER, Value = nuget.org username (NOT email)Optional: add Required reviewers for an approval gate.
⚠️ Wait for the user to confirm they've created the policy before asking them to remove old API keys/secrets or before attempting to run/publish with the workflow. Drafting or showing the workflow file itself is OK before confirmation.
Phase 4: Workflow Setup
Create or modify the publish workflow. The workflow must always be created or shown in your response — you may draft/show it even if the nuget.org policy is not yet confirmed, but do not guide the user to actually run/publish or remove old secrets until after confirmation.
Greenfield: Create publish.yml from the template in references/publish-workflow.md. Adapt .NET version, project path, and environment name. Ensure your output explicitly mentions id-token: write and NuGet/login@v1.
Migration (existing workflow with API key): Modify in place —
Add OIDC permission and environment to the publishing job:
jobs: publish: environment: release permissions: id-token: write # Required — without this, NuGet/login fails with 403 contents: read # Explicit — setting permissions overrides defaultsAdd the NuGet login step before push:
- name: NuGet login (OIDC) id: login uses: NuGet/login@v1 with: user: ${{ secrets.NUGET_USER }} # nuget.org profile name, NOT emailReplace the API key in the push step:
--api-key ${{ steps.login.outputs.NUGET_API_KEY }} --skip-duplicateVerify: Ask the user to trigger a publish and confirm the package appears on nuget.org.
❌ Don't delete the old API key secret until trusted publishing is verified. Removing it is a one-way door — wait for confirmation.
Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
NuGet/login 403 |
Missing id-token: write |
Add to job permissions |
| "no matching policy" | Workflow filename mismatch | Verify exact filename on nuget.org |
| Push unauthorized | Package not owned by policy account | Check policy owner on nuget.org |
| Token expired | Login step >1hr before push | Move NuGet/login closer to push |
| "temporarily active" policy | Private repo, first publish pending | Publish within 7 days |
already_exists on push |
Re-running same version | Add --skip-duplicate |
| GitHub Release 422 | Duplicate release for tag | Delete conflicting release (confirm first) |
| Re-run uses wrong YAML | gh run rerun replays original commit's YAML |
Delete obstacle, re-run — never re-tag |
⚠️ If any blocker persists after one fix attempt, stop and ask the user.
References
- Package type details: references/package-types.md — detection logic, required properties, minimal .csproj examples
- Publish workflow template: references/publish-workflow.md — complete tag-triggered workflow ready to adapt
- Microsoft docs: NuGet Trusted Publishing
Files (skills)
-
references
-
package-types.md 8.2 KB
# NuGet Package Type Reference Structural requirements for each NuGet package type. The agent uses this to validate a repo's packaging setup before configuring trusted publishing. ## Detection Logic Inspect `.csproj` files (and `Directory.Build.props` if present) for these MSBuild properties: ``` 1. Has <PackageType>Template</PackageType>? → Template package 2. Has <PackageType>McpServer</PackageType>? → MCP server (also a dotnet tool) 3. Has <PackAsTool>true</PackAsTool>? → Dotnet tool 4. Has <IsPackable>true</IsPackable> or no OutputType? → NuGet library 5. Has <OutputType>Exe</OutputType> + <IsPackable>true</IsPackable>? → Application package 6. Has <OutputType>Exe</OutputType> without PackAsTool or IsPackable? → Not packable by default (ask user) ``` Check in order — MCP servers have `PackAsTool` too, so `PackageType` must be checked first. ## NuGet Library The most common case. A class library consumed via `PackageReference`. ### Required Properties | Property | Example | Notes | |----------|---------|-------| | `PackageId` | `Contoso.Utilities` | Defaults to `AssemblyName` if omitted | | `Version` | `0.1.0` | Start with 0.x for initial development | ### Recommended Properties | Property | Purpose | |----------|---------| | `Authors` | Package author(s) | | `Description` | Shown on nuget.org | | `PackageTags` | Discoverability | | `PackageReadmeFile` | README displayed on nuget.org | | `PackageLicenseExpression` | SPDX license identifier | | `RepositoryUrl` | Link back to source | | `PublishRepositoryUrl` | Enables source-link integration | ### Including README in Package `PackageReadmeFile` alone isn't enough — you must also include the file in the package: ```xml <PropertyGroup> <PackageReadmeFile>README.md</PackageReadmeFile> </PropertyGroup> <ItemGroup> <None Include="README.md" Pack="true" PackagePath="/" /> <!-- If README is in a parent dir (common for src/ layouts): --> <!-- <None Include="../../README.md" Pack="true" PackagePath="/" /> --> </ItemGroup> ``` ### Minimal .csproj ```xml <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net9.0</TargetFramework> <PackageId>Contoso.Utilities</PackageId> <Version>0.1.0</Version> <Authors>Contoso</Authors> <Description>Utility library for Contoso apps</Description> <PackageReadmeFile>README.md</PackageReadmeFile> </PropertyGroup> <ItemGroup> <None Include="README.md" Pack="true" PackagePath="/" /> </ItemGroup> </Project> ``` ### Pack Command ```bash dotnet pack -c Release ``` ## Dotnet Tool A console app distributed as a global or local tool via `dotnet tool install`. ### Required Properties | Property | Example | Notes | |----------|---------|-------| | `OutputType` | `Exe` | Must be an executable | | `PackAsTool` | `true` | Marks this as a tool package | | `PackageId` | `contoso-cli` | Tool package identifier | | `Version` | `0.1.0` | Package version (or set in Directory.Build.props) | ### Recommended Properties | Property | Example | Notes | |----------|---------|-------| | `ToolCommandName` | `contoso` | Command users type; defaults to assembly name | | `PackageOutputPath` | `./nupkg` | Where .nupkg is written | | `PackageReadmeFile` | `README.md` | Shown on nuget.org | ### Minimal .csproj ```xml <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net9.0</TargetFramework> <PackAsTool>true</PackAsTool> <ToolCommandName>contoso</ToolCommandName> <PackageId>contoso-cli</PackageId> <PackageReadmeFile>README.md</PackageReadmeFile> </PropertyGroup> <ItemGroup> <None Include="README.md" Pack="true" PackagePath="/" /> </ItemGroup> </Project> ``` ### Pack Command ```bash dotnet pack -c Release ``` ## MCP Server A dotnet tool that implements the Model Context Protocol. Distributed the same way as a dotnet tool but with additional metadata for MCP client discovery. ### Naming Convention Follow the established pattern for MCP server packages: - **PackageId**: `{github-username}.{domain}.mcp` (e.g., `lewing.helix.mcp`) - **server.json `name`**: `io.github.{username}/{packageid}` (e.g., `io.github.lewing/lewing.helix.mcp`) ### Required Properties Everything from Dotnet Tool, plus: | Property | Example | Notes | |----------|---------|-------| | `PackageType` | `McpServer` | NuGet recognizes this as an MCP server | ### Recommended Properties | Property | Example | Notes | |----------|---------|-------| | `McpServerJsonTemplateFile` | `.mcp/server.json` | MCP client discovery metadata | ### Required Files | File | Purpose | |------|---------| | `.mcp/server.json` | MCP server descriptor for client discovery | The `.mcp/server.json` must be included in the package: ```xml <ItemGroup> <None Include=".mcp/server.json" Pack="true" PackagePath="/.mcp/" /> </ItemGroup> ``` ### Minimal server.json ```json { "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json", "name": "io.github.contoso/contoso.services.mcp", "description": "MCP server for Contoso services", "version": "0.1.0", "packages": [ { "registryType": "nuget", "registryBaseUrl": "https://api.nuget.org", "identifier": "contoso.services.mcp", "version": "0.1.0", "transport": { "type": "stdio" } } ] } ``` > ⚠️ **Version sync**: The `version` fields in `server.json` MUST match the `<Version>` in your `.csproj`. Update both when bumping versions. > ⚠️ **nuget.org MCP Server tab**: nuget.org auto-generates MCP install config from your `server.json`. If your tool requires a subcommand (e.g., `my-tool mcp`), the generated config may omit it. Ensure your tool defaults to MCP server mode when invoked with no arguments, or uses a `--yes` flag for non-interactive acceptance. ### Minimal .csproj ```xml <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net9.0</TargetFramework> <PackAsTool>true</PackAsTool> <PackageType>McpServer</PackageType> <PackageId>contoso.services.mcp</PackageId> <ToolCommandName>contoso-mcp</ToolCommandName> <McpServerJsonTemplateFile>.mcp/server.json</McpServerJsonTemplateFile> <PackageReadmeFile>README.md</PackageReadmeFile> </PropertyGroup> <ItemGroup> <None Include=".mcp/server.json" Pack="true" PackagePath="/.mcp/" /> <None Include="README.md" Pack="true" PackagePath="/" /> </ItemGroup> </Project> ``` ## Template Package A package containing `dotnet new` templates. ### Required Properties | Property | Example | Notes | |----------|---------|-------| | `PackageType` | `Template` | NuGet recognizes this as a template package | | `PackageId` | `Contoso.Templates` | Template package identifier | ### Required Files | File | Purpose | |------|---------| | `content/*/.template.config/template.json` | Template definition — one per template | The `template.json` must include at minimum: `identity`, `name`, `shortName`, `tags.type` (`item` or `project`). ### Minimal .csproj ```xml <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <PackageType>Template</PackageType> <PackageId>Contoso.Templates</PackageId> <Version>0.1.0</Version> <Description>Contoso project templates</Description> <!-- Template packages should not build code --> <IncludeContentInPack>true</IncludeContentInPack> <IncludeBuildOutput>false</IncludeBuildOutput> <ContentTargetFolders>content</ContentTargetFolders> <NoDefaultExcludes>true</NoDefaultExcludes> </PropertyGroup> <ItemGroup> <Content Include="content/**" /> </ItemGroup> </Project> ``` ## Common Gotchas - **`IsPackable` defaults**: Class libraries default to `true`, console apps to `false`. Console apps can still be published as NuGet packages by setting `<IsPackable>true</IsPackable>` — they just won't be installable via `dotnet tool install` unless `PackAsTool` is also set. - **`Directory.Build.props`**: Package metadata may be set at the repo root — always check there too. - **Multi-project repos**: A repo may contain multiple packable projects of different types. Each needs its own trusted publishing workflow or a matrix build. - **`GeneratePackageOnBuild`**: If `true`, `dotnet build` also produces the `.nupkg`. The workflow should use `dotnet pack` explicitly for clarity. -
publish-workflow.md 3.8 KB
# Publish Workflow Template Complete tag-triggered GitHub Actions workflow for publishing NuGet packages with trusted publishing. Copy and adapt to your repo. ## Template ```yaml name: Publish to NuGet on: push: tags: - 'v*' # Triggers on version tags: v1.0.0, v1.2.3-preview.1, etc. jobs: publish: runs-on: ubuntu-latest environment: release # Uses release environment for secret scoping + protection rules permissions: id-token: write # Required for OIDC token (NuGet trusted publishing) contents: read steps: - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: '9.0.x' # Adjust to your target framework - name: Extract version from tag id: version run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT - name: Validate version matches project run: | PROJECT_VERSION=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' path/to/YourProject.csproj) if [ "$PROJECT_VERSION" != "${{ steps.version.outputs.VERSION }}" ]; then echo "::error::Tag version (${{ steps.version.outputs.VERSION }}) doesn't match project version ($PROJECT_VERSION)" exit 1 fi - name: Pack run: dotnet pack path/to/YourProject.csproj -c Release -o ./artifacts - name: NuGet login (OIDC) id: login uses: NuGet/login@v1 with: user: ${{ secrets.NUGET_USER }} # nuget.org profile name (NOT email) - name: Push to NuGet run: dotnet nuget push ./artifacts/*.nupkg --api-key ${{ steps.login.outputs.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate ``` ## Customization Points | Item | What to change | |------|---------------| | `dotnet-version` | Match your `TargetFramework` | | `path/to/YourProject.csproj` | Path to your packable project | | Version extraction `sed` | Adjust for `Directory.Build.props` or .NET 10 file-based apps (`#:property Version=`) | | `--skip-duplicate` | Keeps push idempotent — safe for re-runs and matrix builds | ## Release Process Once the workflow is committed, the publish process is: ```bash # 1. Bump version in .csproj (and server.json for MCP servers) # 2. Commit git add -A && git commit -m "Bump version to 0.1.0" # 3. Tag and push git tag v0.1.0 git push origin main --tags # 4. Workflow runs automatically, publishes to nuget.org ``` ## Optional: GitHub Release Step Add after the push step if you want GitHub Releases with the `.nupkg` attached. Note: this requires changing `contents: read` to `contents: write` in the job permissions. ```yaml - name: Create GitHub Release uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2 with: files: ./artifacts/*.nupkg generate_release_notes: true ``` > ⚠️ **Consider omitting this step entirely.** Creating GitHub Releases separately (manually or via `gh release create` in a different workflow) avoids 422 `already_exists` conflicts and keeps the publish workflow focused on NuGet. If any release step fails, it blocks NuGet publishing too. > ⚠️ **Don't use `ncipollo/release-action` AND `gh release create` for the same tag** — this causes HTTP 422 `already_exists` errors. > ⚠️ **`gh run rerun` replays the original YAML** from the tag commit, not from `main`. If the workflow fails due to a release conflict, delete the conflicting release and re-run — don't delete the tag and re-tag (NuGet package IDs are permanent). ## CI vs Publish Separation Keep your CI workflow (build + test on PR/push) separate from the publish workflow (tag-triggered). This gives you: - CI runs on every PR without publishing - Publish only runs on deliberate version tags - Different permission scopes (CI doesn't need `id-token: write`)
-
-
SKILL.md 9.1 KB
--- name: nuget-trusted-publishing description: > Set up NuGet trusted publishing (OIDC) on a GitHub Actions repo — replaces long-lived API keys with short-lived tokens. USE FOR: trusted publishing, NuGet OIDC, keyless NuGet publish, migrate from NuGet API key, NuGet/login, secure NuGet publishing. DO NOT USE FOR: publishing to private feeds or Azure Artifacts (OIDC is nuget.org only). INVOKES: shell (powershell or bash), edit, create, ask_user for guided repo setup. license: MIT --- # NuGet Trusted Publishing Setup Set up [NuGet trusted publishing](https://learn.microsoft.com/en-us/nuget/nuget-org/trusted-publishing) on a GitHub Actions repo. Replaces long-lived API keys with OIDC-based short-lived tokens — no secrets to rotate or leak. ## Prerequisites - **GitHub Actions** — this skill covers GitHub Actions setup only - **nuget.org account** — the user needs access to create trusted publishing policies ## When to Use This Skill Use this skill when: - Setting up trusted publishing for a NuGet package - Migrating from `secrets.NUGET_API_KEY` to OIDC-based publishing - Asked about keyless or secure NuGet publishing - Creating a new NuGet publish workflow from scratch - Asked to "remove NuGet API key" or "use NuGet/login" - Setting up publishing for a dotnet tool, MCP server, or template package - Asked about `NuGet/login@v1` or `id-token: write` ## Safety Rules > ⚠️ **Bail-out rule**: If any phase fails after one fix attempt on an infrastructure/auth issue, stop and ask the user. Don't loop on environment problems. > ⚠️ **Never delete or overwrite without confirmation**: Removing API key secrets, deleting tags/releases, removing workflow steps, or changing package IDs. NuGet package IDs are permanent — mistakes can't be undone. ## Process > **Fast-path for greenfield repos**: When the user has a simple setup (one packable project, no existing publish workflow), don't gate on multi-turn assessment. Combine phases: create the workflow immediately, include nuget.org policy guidance, local pack recommendation, and filename-matching warning all in one response. The full phased process below is for complex or migration scenarios. ### Phase 1: Assess Inspect the repo and report findings before making any changes. 1. **Find and classify packable projects** — check `.csproj` files **and `Directory.Build.props`** (package metadata is often set repo-wide). Classify in this order (earlier matches win): - `<PackageType>Template</PackageType>` → **Template** - `<PackageType>McpServer</PackageType>` → **MCP server** (also a dotnet tool) - `<PackAsTool>true</PackAsTool>` → **Dotnet tool** - Class library (`IsPackable=true` or no `OutputType`) → **Library** - `<OutputType>Exe</OutputType>` with `<IsPackable>true</IsPackable>` → **Application package** (not a tool, but still publishable) - `<OutputType>Exe</OutputType>` without `PackAsTool` or `IsPackable` → Not packable by default (ask user if they intend to publish it) 2. **Validate structure** for each project's type: | Type | Required | |------|----------| | All | `PackageId`, `Version` (in .csproj or Directory.Build.props) | | Dotnet tool | `PackAsTool` (required); `ToolCommandName` (optional but recommended — defaults to assembly name) | | MCP server | `PackageType=McpServer`, `.mcp/server.json` included in package | | Template | `PackageType=Template`, `.template.config/template.json` under content dir | 3. **Find existing publish workflows** in `.github/workflows/` — look for `dotnet nuget push`, `nuget push`, or `dotnet pack`. 4. **Check version consistency** — for MCP servers, verify `.csproj` `<Version>` matches both `server.json` version fields (root `version` and `packages[].version`). Flag any mismatch. 5. **Report findings** to the user: classification, missing properties, version mismatches, existing workflows. For multi-project repos, note whether one workflow or separate workflows per package are needed. Offer to fix gaps — use `ask_user` before modifying project files. > ❌ See [references/package-types.md](references/package-types.md) for per-type details and required properties. ### Phase 2: Local Verification Pack and verify locally before touching nuget.org — publishing errors waste a permanent version number. > ⚠️ **Always mention this step**, even if you defer running it. Tell the user: "Before your first publish, run `dotnet pack -c Release -o ./artifacts` to verify the .nupkg is created correctly." 1. `dotnet pack -c Release -o ./artifacts` — verify `.nupkg` is created 2. For tools/MCP servers: install from `./artifacts`, run `--help`, uninstall 3. For libraries: inspect the `.nupkg` contents (it's a zip) ### Phase 3: nuget.org Policy This phase requires the user to act on nuget.org — guide them with exact values. 1. Determine the **repo owner**, **repo name**, and the **workflow filename** that will publish. > ❌ The policy requires the **exact workflow filename** (e.g., `publish.yml` or `publish.yaml`) — just the filename, no path prefix. Matching is case-insensitive. Don't use the workflow `name:` field. 2. Guide the user to create the trusted publishing policy: > Go to [**nuget.org/account/trustedpublishing**](https://www.nuget.org/account/trustedpublishing) → **Add policy** > > - **Repository Owner**: `{owner}` > - **Repository**: `{repo}` > - **Workflow File**: `{filename}.yml` > - **Environment**: `release` *(only if the workflow uses `environment:`; leave blank otherwise)* Policy ownership: the user chooses individual account or organization. Org-owned policies apply to all packages owned by that org. For **private repos**: policy is "temporarily active" for 7 days — becomes permanent after the first successful publish. 3. Guide the user to create a **GitHub Environment** (recommended but optional — provides secret scoping + approval gates): > Repo **Settings** → **Environments** → **New environment** → `release` > > Add environment secret: **Name** = `NUGET_USER`, **Value** = nuget.org username (NOT email) Optional: add **Required reviewers** for an approval gate. > ⚠️ Wait for the user to confirm they've created the policy **before asking them to remove old API keys/secrets or before attempting to run/publish with the workflow**. Drafting or showing the workflow file itself is OK before confirmation. ### Phase 4: Workflow Setup Create or modify the publish workflow. **The workflow must always be created or shown in your response** — you may draft/show it even if the nuget.org policy is not yet confirmed, but do not guide the user to actually run/publish or remove old secrets until after confirmation. **Greenfield**: Create `publish.yml` from the template in [references/publish-workflow.md](references/publish-workflow.md). Adapt .NET version, project path, and environment name. Ensure your output explicitly mentions `id-token: write` and `NuGet/login@v1`. **Migration** (existing workflow with API key): Modify in place — 1. **Add OIDC permission and environment** to the publishing job: ```yaml jobs: publish: environment: release permissions: id-token: write # Required — without this, NuGet/login fails with 403 contents: read # Explicit — setting permissions overrides defaults ``` 2. **Add the NuGet login step** before push: ```yaml - name: NuGet login (OIDC) id: login uses: NuGet/login@v1 with: user: ${{ secrets.NUGET_USER }} # nuget.org profile name, NOT email ``` 3. **Replace the API key** in the push step: ```yaml --api-key ${{ steps.login.outputs.NUGET_API_KEY }} --skip-duplicate ``` 4. **Verify**: Ask the user to trigger a publish and confirm the package appears on nuget.org. > ❌ **Don't delete the old API key secret** until trusted publishing is verified. Removing it is a one-way door — wait for confirmation. ## Troubleshooting | Problem | Cause | Fix | |---------|-------|-----| | `NuGet/login` 403 | Missing `id-token: write` | Add to job permissions | | "no matching policy" | Workflow filename mismatch | Verify exact filename on nuget.org | | Push unauthorized | Package not owned by policy account | Check policy owner on nuget.org | | Token expired | Login step >1hr before push | Move `NuGet/login` closer to push | | "temporarily active" policy | Private repo, first publish pending | Publish within 7 days | | `already_exists` on push | Re-running same version | Add `--skip-duplicate` | | GitHub Release 422 | Duplicate release for tag | Delete conflicting release (confirm first) | | Re-run uses wrong YAML | `gh run rerun` replays original commit's YAML | Delete obstacle, re-run — never re-tag | > ⚠️ If any blocker persists after one fix attempt, **stop and ask the user**. ## References - **Package type details**: [references/package-types.md](references/package-types.md) — detection logic, required properties, minimal .csproj examples - **Publish workflow template**: [references/publish-workflow.md](references/publish-workflow.md) — complete tag-triggered workflow ready to adapt - **Microsoft docs**: [NuGet Trusted Publishing](https://learn.microsoft.com/en-us/nuget/nuget-org/trusted-publishing)
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.