{
  "schema": "https://ai-atoms.com/schemas/skill-v1.json",
  "type": "skill",
  "id": "skill/nuget-trusted-publishing",
  "version": "1.0.1",
  "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.",
  "system_prompt_fragment": "# NuGet Trusted Publishing Setup\n\nSet 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.\n\n## Prerequisites\n\n- **GitHub Actions** — this skill covers GitHub Actions setup only\n- **nuget.org account** — the user needs access to create trusted publishing policies\n\n## When to Use This Skill\n\nUse this skill when:\n- Setting up trusted publishing for a NuGet package\n- Migrating from `secrets.NUGET_API_KEY` to OIDC-based publishing\n- Asked about keyless or secure NuGet publishing\n- Creating a new NuGet publish workflow from scratch\n- Asked to \"remove NuGet API key\" or \"use NuGet/login\"\n- Setting up publishing for a dotnet tool, MCP server, or template package\n- Asked about `NuGet/login@v1` or `id-token: write`\n\n## Safety Rules\n\n> ⚠️ **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.\n\n> ⚠️ **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.\n\n## Process\n\n> **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.\n\n### Phase 1: Assess\n\nInspect the repo and report findings before making any changes.\n\n1. **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):\n   - `<PackageType>Template</PackageType>` → **Template**\n   - `<PackageType>McpServer</PackageType>` → **MCP server** (also a dotnet tool)\n   - `<PackAsTool>true</PackAsTool>` → **Dotnet tool**\n   - Class library (`IsPackable=true` or no `OutputType`) → **Library**\n   - `<OutputType>Exe</OutputType>` with `<IsPackable>true</IsPackable>` → **Application package** (not a tool, but still publishable)\n   - `<OutputType>Exe</OutputType>` without `PackAsTool` or `IsPackable` → Not packable by default (ask user if they intend to publish it)\n\n2. **Validate structure** for each project's type:\n\n   | Type | Required |\n   |------|----------|\n   | All | `PackageId`, `Version` (in .csproj or Directory.Build.props) |\n   | Dotnet tool | `PackAsTool` (required); `ToolCommandName` (optional but recommended — defaults to assembly name) |\n   | MCP server | `PackageType=McpServer`, `.mcp/server.json` included in package |\n   | Template | `PackageType=Template`, `.template.config/template.json` under content dir |\n\n3. **Find existing publish workflows** in `.github/workflows/` — look for `dotnet nuget push`, `nuget push`, or `dotnet pack`.\n\n4. **Check version consistency** — for MCP servers, verify `.csproj` `<Version>` matches both `server.json` version fields (root `version` and `packages[].version`). Flag any mismatch.\n\n5. **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.\n\n> ❌ See [references/package-types.md](references/package-types.md) for per-type details and required properties.\n\n### Phase 2: Local Verification\n\nPack and verify locally before touching nuget.org — publishing errors waste a permanent version number.\n\n> ⚠️ **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.\"\n\n1. `dotnet pack -c Release -o ./artifacts` — verify `.nupkg` is created\n2. For tools/MCP servers: install from `./artifacts`, run `--help`, uninstall\n3. For libraries: inspect the `.nupkg` contents (it's a zip)\n\n### Phase 3: nuget.org Policy\n\nThis phase requires the user to act on nuget.org — guide them with exact values.\n\n1. Determine the **repo owner**, **repo name**, and the **workflow filename** that will publish.\n\n   > ❌ 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.\n\n2. Guide the user to create the trusted publishing policy:\n   > Go to [**nuget.org/account/trustedpublishing**](https://www.nuget.org/account/trustedpublishing) → **Add policy**\n   >\n   > - **Repository Owner**: `{owner}`\n   > - **Repository**: `{repo}`\n   > - **Workflow File**: `{filename}.yml`\n   > - **Environment**: `release` *(only if the workflow uses `environment:`; leave blank otherwise)*\n\n   Policy ownership: the user chooses individual account or organization. Org-owned policies apply to all packages owned by that org.\n\n   For **private repos**: policy is \"temporarily active\" for 7 days — becomes permanent after the first successful publish.\n\n3. Guide the user to create a **GitHub Environment** (recommended but optional — provides secret scoping + approval gates):\n   > Repo **Settings** → **Environments** → **New environment** → `release`\n   >\n   > Add environment secret: **Name** = `NUGET_USER`, **Value** = nuget.org username (NOT email)\n\n   Optional: add **Required reviewers** for an approval gate.\n\n> ⚠️ 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.\n\n### Phase 4: Workflow Setup\n\nCreate 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.\n\n**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`.\n\n**Migration** (existing workflow with API key): Modify in place —\n\n1. **Add OIDC permission and environment** to the publishing job:\n   ```yaml\n   jobs:\n     publish:\n       environment: release\n       permissions:\n         id-token: write     # Required — without this, NuGet/login fails with 403\n         contents: read      # Explicit — setting permissions overrides defaults\n   ```\n\n2. **Add the NuGet login step** before push:\n   ```yaml\n   - name: NuGet login (OIDC)\n     id: login\n     uses: NuGet/login@v1\n     with:\n       user: ${{ secrets.NUGET_USER }}  # nuget.org profile name, NOT email\n   ```\n\n3. **Replace the API key** in the push step:\n   ```yaml\n   --api-key ${{ steps.login.outputs.NUGET_API_KEY }} --skip-duplicate\n   ```\n\n4. **Verify**: Ask the user to trigger a publish and confirm the package appears on nuget.org.\n\n> ❌ **Don't delete the old API key secret** until trusted publishing is verified. Removing it is a one-way door — wait for confirmation.\n\n## Troubleshooting\n\n| Problem | Cause | Fix |\n|---------|-------|-----|\n| `NuGet/login` 403 | Missing `id-token: write` | Add to job permissions |\n| \"no matching policy\" | Workflow filename mismatch | Verify exact filename on nuget.org |\n| Push unauthorized | Package not owned by policy account | Check policy owner on nuget.org |\n| Token expired | Login step >1hr before push | Move `NuGet/login` closer to push |\n| \"temporarily active\" policy | Private repo, first publish pending | Publish within 7 days |\n| `already_exists` on push | Re-running same version | Add `--skip-duplicate` |\n| GitHub Release 422 | Duplicate release for tag | Delete conflicting release (confirm first) |\n| Re-run uses wrong YAML | `gh run rerun` replays original commit's YAML | Delete obstacle, re-run — never re-tag |\n\n> ⚠️ If any blocker persists after one fix attempt, **stop and ask the user**.\n\n## References\n\n- **Package type details**: [references/package-types.md](references/package-types.md) — detection logic, required properties, minimal .csproj examples\n- **Publish workflow template**: [references/publish-workflow.md](references/publish-workflow.md) — complete tag-triggered workflow ready to adapt\n- **Microsoft docs**: [NuGet Trusted Publishing](https://learn.microsoft.com/en-us/nuget/nuget-org/trusted-publishing)",
  "applicable_domains": [
    "code",
    "dotnet",
    "engineering"
  ],
  "invocation": [
    "/nuget-trusted-publishing"
  ],
  "tags": [
    "dotnet",
    "csharp",
    "microsoft"
  ],
  "authored_by": "anthropics",
  "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-advanced/skills/nuget-trusted-publishing/SKILL.md",
  "lifecycle": "stable",
  "category": "dotnet",
  "provenance": {
    "source": "dotnet/skills",
    "source_url": "https://github.com/dotnet/skills/blob/main/plugins/dotnet-advanced/skills/nuget-trusted-publishing/SKILL.md",
    "author": "Microsoft / .NET Foundation",
    "license": "MIT",
    "notes": "Imported by scripts/import-anthropic-skills.py."
  }
}