Adopting the hub-hosted release chain breaks NuGet.org OIDC trusted publishing, because the OIDC token's job_workflow_ref claim then names the hub's workflow rather than the publishing repository's, and NuGet validates that claim against the repository the package belongs to.
Evidence
First real (non-smoke) publish from ptr727/Utilities after it adopted build-release-task.yml at f3b4cc9 (2.0.526). Run 33336000403, Build NuGet library job, Run hub build-nuget default step:
##[error]Token exchange failed (HTTP 401) at https://www.nuget.org/api/v2/token.
Make sure you are using the username of the policy creator, not the policy owner:
Claim 'job_workflow_ref' has value
'<owner>/ProjectTemplate/.github/workflows/build-release-task.yml@f3b4cc98654878e63ebfe21b68b8516b7bb46469'
which does not start with <owner>/Utilities/.github/workflows/.
The build itself succeeded with 0 errors, and every later job was skipped, so the run published nothing and left no partial release. Plan, Validate (all three jobs), Get version and Validate release version all passed. The failure is exactly and only the token exchange.
Why this is structural rather than a misconfiguration
job_workflow_ref identifies the reusable workflow the job actually ran from, so any hub-hosted release task produces the hub's ref. That is not something the calling repository can change:
- A caller hook does not help.
build-release-task.yml already supports .github/actions/build-nuget/action.yml, but a composite action runs inside the hub's reusable workflow, so the claim is unchanged.
- Only the trigger and wiring stay in the caller stub, which is the design. The push necessarily happens in the hub task.
So every NuGet-publishing repository that adopts the release chain hits this on its first real publish. It cannot be caught earlier, because a smoke build never reaches NuGet/login: nuget: false and smoke: true both gate it off, which is why this repository's pull requests were green throughout.
This is likely the first time the hub-hosted NuGet leg has run for real. PhotoCleaner, the documented pilot, sets enable_nuget: false and exercises the dotnet-publish and Docker legs instead.
What the docs currently say
AUDIT.md's nuget check, README.md, repo-config/README.md and nuget-push-default/action.yml all describe OIDC trusted publishing as keyless and needing only id-token: write plus NUGET_USERNAME. None mentions the job_workflow_ref constraint or that adopting the hub task changes it. docs/reusable-workflows.md "Adopting the Release Chain" gives the NuGet-library stub with nuget: true and nuget_project, with no note that the trusted-publishing policy has to move first.
The decision this needs, which is not the agent's to make
Pointing the NuGet policy at the hub's workflow file would let any repository calling that hub task publish the package, since the claim no longer names the owning repository. That widens who can publish a package, so it is a deliberate trade rather than a settings fix, and it belongs with the maintainer and in the docs either way.
The alternative is to keep the NuGet push in the calling repository, either by leaving enable_nuget: false on the hub task and carrying a small local publish job, or by having the hub task delegate the push itself back to the caller so the claim stays repository-local.
Whichever way it goes, docs/reusable-workflows.md "Adopting the Release Chain" and the nuget check in AUDIT.md should state the constraint, so the next adopter learns it before a release fails rather than during one.
Current state of the affected repository
ptr727/Utilities main is at baa92ec, carrying the resync, with no release cut. Its previous release, 4.0.38, is unaffected and remains the latest on both GitHub Releases and NuGet.org.
Adopting the hub-hosted release chain breaks NuGet.org OIDC trusted publishing, because the OIDC token's
job_workflow_refclaim then names the hub's workflow rather than the publishing repository's, and NuGet validates that claim against the repository the package belongs to.Evidence
First real (non-smoke) publish from
ptr727/Utilitiesafter it adoptedbuild-release-task.ymlatf3b4cc9(2.0.526). Run 33336000403,Build NuGet library job,Run hub build-nuget default step:The build itself succeeded with 0 errors, and every later job was skipped, so the run published nothing and left no partial release.
Plan,Validate(all three jobs),Get versionandValidate release versionall passed. The failure is exactly and only the token exchange.Why this is structural rather than a misconfiguration
job_workflow_refidentifies the reusable workflow the job actually ran from, so any hub-hosted release task produces the hub's ref. That is not something the calling repository can change:build-release-task.ymlalready supports.github/actions/build-nuget/action.yml, but a composite action runs inside the hub's reusable workflow, so the claim is unchanged.So every NuGet-publishing repository that adopts the release chain hits this on its first real publish. It cannot be caught earlier, because a smoke build never reaches
NuGet/login:nuget: falseandsmoke: trueboth gate it off, which is why this repository's pull requests were green throughout.This is likely the first time the hub-hosted NuGet leg has run for real.
PhotoCleaner, the documented pilot, setsenable_nuget: falseand exercises thedotnet-publishand Docker legs instead.What the docs currently say
AUDIT.md'snugetcheck,README.md,repo-config/README.mdandnuget-push-default/action.ymlall describe OIDC trusted publishing as keyless and needing onlyid-token: writeplusNUGET_USERNAME. None mentions thejob_workflow_refconstraint or that adopting the hub task changes it.docs/reusable-workflows.md"Adopting the Release Chain" gives the NuGet-library stub withnuget: trueandnuget_project, with no note that the trusted-publishing policy has to move first.The decision this needs, which is not the agent's to make
Pointing the NuGet policy at the hub's workflow file would let any repository calling that hub task publish the package, since the claim no longer names the owning repository. That widens who can publish a package, so it is a deliberate trade rather than a settings fix, and it belongs with the maintainer and in the docs either way.
The alternative is to keep the NuGet push in the calling repository, either by leaving
enable_nuget: falseon the hub task and carrying a small local publish job, or by having the hub task delegate the push itself back to the caller so the claim stays repository-local.Whichever way it goes,
docs/reusable-workflows.md"Adopting the Release Chain" and thenugetcheck inAUDIT.mdshould state the constraint, so the next adopter learns it before a release fails rather than during one.Current state of the affected repository
ptr727/Utilitiesmainis atbaa92ec, carrying the resync, with no release cut. Its previous release,4.0.38, is unaffected and remains the latest on both GitHub Releases and NuGet.org.