Move the Release Task's Job Scopes to the Caller - #772
Conversation
The hub's first release through build-release-task.yml failed at startup: build-nuget declared id-token: write at job level, and a called job's permissions block is validated against the caller's grant before its if: runs, so a caller that does not grant that scope fails the whole workflow even with the job disabled. github-release carried the same trap for contents and actions write, which a smoke build under a read-only pull request token could never grant. Both blocks go, the caller grants what the targets it enables need, and the doc says so.
There was a problem hiding this comment.
Pull request overview
This PR updates the hub’s reusable release workflow to avoid startup failures caused by job-level permissions: blocks that are validated against the caller’s token grant even when the job is skipped. It shifts responsibility for granting write/OIDC scopes to the caller workflow and documents the required caller-side permissions.
Changes:
- Remove job-level
permissions:frombuild-nugetandgithub-releaseinbuild-release-task.yml. - Expand
docs/reusable-workflows.mdguidance so adopters grant the needed permissions at the caller job level.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| docs/reusable-workflows.md | Documents caller-granted permissions for release-chain adoption. |
| .github/workflows/build-release-task.yml | Removes job-level permissions to prevent reusable-workflow startup validation failures. |
Suppressed comments (1)
.github/workflows/build-release-task.yml:536
- This comment implies
contents: writeandactions: writeare needed for any publish, but they are only required when this job is enabled (inputs.githubis true andinputs.smokeis false). Making that explicit helps callers avoid granting write scopes when they do not create a GitHub release.
# The release upload and the artifact-delete cleanup both write with GITHUB_TOKEN, and the caller grants contents: write and actions: write on a publish.
# No job-level permissions: block here, for the reason build-nuget gives: a block is validated against the caller's grant before if: runs, and a smoke caller holds a read-only token.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Answering the suppressed finding, "Suppressed comments (1)", from the round on |
Promote `develop` to `main`, carrying two changes: - #772 Move the Release Task's Job Scopes to the Caller: `build-release-task.yml` declares no job-level `permissions:` on `build-nuget` or `github-release`, since a called job's block is validated against the caller's grant before its `if:` and failed the hub's own release at startup (run 31972504539 on `82fecef`). The caller grants what its enabled paths write with. - #771 Bump astral-sh/setup-uv from 8.1.0 to 10.0.0 (Dependabot, auto-merged to `develop` by the App through the hub-hosted merge-bot task, run 31972622149, the same bump #770 merged to `main` through it, run 31972611554, so both live proofs of the stage-0 tracker are in). The release dispatched after this merge is the live proof of the scope fix: the hub's `publish` job grants `contents: write` and `actions: write`, every target is disabled, and `github-release` must run and create the tag.
…nippets (#775) Release `2.0.352` (`main` `0b07a59d7c65d07d8df275a96deaf2e06cbefd51`) is the first tag carrying every hub-hosted reusable workflow task, produced by [the publish-release run](https://github.com/ptr727/ProjectTemplate/actions/runs/31974067182) after promotion #774. In that run `build-release-task.yml` ran with every target disabled and its `github-release` job succeeded while all five build jobs skipped, proving the `github-release` skip fix (#762) and the caller-granted scopes (#772). The earlier release attempt on `82fecef` ([run](https://github.com/ptr727/ProjectTemplate/actions/runs/31972504539)) ended in `startup_failure` because `build-nuget`/`github-release` declared job-level permissions; #772 fixed it. ## docs/reusable-workflows.md "Rollout" - Ticks the two stage-0 merge-bot proof items: Dependabot PR #771 merged to `develop` with `--squash` through `merge-bot-task.yml` ([run](https://github.com/ptr727/ProjectTemplate/actions/runs/31972622149)), and Dependabot PR #770 merged to `main` with `--merge` ([run](https://github.com/ptr727/ProjectTemplate/actions/runs/31972611554)). - Ticks "Promoted and released" in stages 2, 3, 4, and 5 with `2.0.352` on `0b07a59d` and #774, and stage 4 also notes the first attempt's `startup_failure` and its #772 fix. - Leaves every adoption and pilot checkbox untouched. - Does **not** tick either stage's "Catalog snippets ... pinned to that release" item, since neither stage's full set of snippets is complete this time: stage 2's release-with-smoke shape still calls its own repo's `build-release-task.yml` by `./` path rather than the hub's, and stage 5's `deploy-site.yml`, `publish-docker-readme-task.yml`, and `check-upstream-version-task.yml` each stay open for reasons the doc itself already states (no manifest-wide snippet by design, or a job embedded in a larger stub rather than a standalone caller). Both items gain an inline note identifying which of their snippets this PR adds. ## New catalog snippets Three of the five/six candidate files, each pinned `0b07a59d7c65d07d8df275a96deaf2e06cbefd51 # 2.0.352` and byte-identical to its doc stub block apart from the pin (and, where the doc's own comment wrapping does not survive an actual YAML file's comment-wrap rule, a reworded comment kept in sync on both sides): - `catalog/snippets/workflows/test-pull-request.yml` (the no-build gate shape from "Adopting the Gates") - `catalog/snippets/workflows/publish-release.yml` (the release-repo shape from "Adopting the Release Chain") - `catalog/snippets/workflows/run-periodic-codegen-pull-request.yml` (the codegen stub, which the doc already says keeps today's per-repo shape) Skipped, with the doc updated to say why: - `test-pull-request-smoke.yml` - the doc's smoke stub still calls the caller's own `./.github/workflows/build-release-task.yml` by local path, never the hub's `build-release-task.yml` directly, so it does not meet the "calls the hub task" condition. - `deploy-site.yml` - the doc's own "Deploy-site" paragraph states it "has no manifest-wide catalog snippet either," the same reason `get-version-task.yml`/`publish-plan-task.yml` have none: each site's own shape varies too much around the shared `deploy` job. - `check-upstream-version.yml` - the doc gives only the `check-upstream-version:` job body, not a complete top-level trigger/concurrency shape, and unlike the codegen stub there is no doc statement that a uniform shape exists to copy. `catalog/snippets/workflows/README.md` gained rows for the three new snippets and an updated intro paragraph explaining the partial rollout. ## TODO.md Added one **Settled** line under "Host the type-specific tasks" naming `2.0.352` as the release that makes stages 2-5 adoptable and the run that proved the release chain, and noting #769 as the one open follow-up from the promotion review. ## reports/workflow-reuse.md Regenerated with `python3 spec/workflow_reuse.py --report` against the live fleet at hub `63d244b` (this branch's base). The numbers move against the last committed report only because the live fleet moved since then, not because of anything in this PR. ## Gates run locally `spec/validate.py`, `python3 -m unittest discover -s scripts/tests` (686 tests), `spec/audit.py --selftest`, `spec/workflow_reuse.py --selftest`, `scripts/repo_gate.py` (sha-pin resolved against GitHub), `scripts/build_dist.py --check`, `scripts/prose_lint.py` (all nine checks), `ruff check`/`ruff format --check`, `mypy`, and the Docker `actionlint`, `markdownlint-cli2`, and `editorconfig-checker` linters (only `.ruff_cache` noise there).
What
build-release-task.ymlno longer declares job-levelpermissions:onbuild-nuget(id-token: write) orgithub-release(contents: write,actions: write). The caller grants what the targets it enables need, anddocs/reusable-workflows.md"Adopting the Release Chain" says so.Why
The hub's first release through the task, dispatched on
mainat82fecefafter #768, ended instartup_failure(run 31972504539). A called job'spermissions:block is validated against the caller's grant before itsif:runs (GOVERNANCE.md "Workflow YAML Conventions", the reusable-workflows bullet), sobuild-nugetasking forid-token: writefails any caller that does not grant it, the hub's ownpublishjob included, even though the job is disabled.github-release's block has the same shape and would fail every smoke build, since a Dependabot pull request holds a read-only token that cannot grantcontents: write. This is the finding #762 declined on the ground that onlyNuGet/loginconsumes the scope: the consumption was never the problem, the declaration was.Verification
actionlint, prose gate, audit self-test, 686 unit tests, markdownlint. The live proof is the re-dispatched hub release after this promotes, which is the next step.