diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c0ef0d9c..b28941d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -265,6 +265,20 @@ jobs: python_only: true permissions: contents: read + # `issues: write` is required even though NOTHING here writes an issue. + # `parity.yml`'s `notify-planner-drift` job declares + # `permissions: {issues: write}`, and a called workflow may never exceed + # the grant its caller gives it. GitHub checks that STATICALLY, before + # any job runs and regardless of the `if:` that keeps that job to + # `repository_dispatch` -- so a caller granting only `contents: read` + # fails the ENTIRE run with `startup_failure` and zero jobs, which is + # what v0.6.0-rc1's first tag push did (run 31830969596). + # + # Grant it here rather than dropping it from `notify-planner-drift`: + # that job genuinely needs it on its own `repository_dispatch` trigger, + # where it files/refreshes the planner-drift tracker. A tag build simply + # never reaches it. + issues: write # One PyInstaller freeze of `python/` per runner. There is no cross-build step # and there cannot be one: PyInstaller freezes the interpreter it is running diff --git a/changelog.d/752.fixed.md b/changelog.d/752.fixed.md new file mode 100644 index 00000000..6f28a2d4 --- /dev/null +++ b/changelog.d/752.fixed.md @@ -0,0 +1,12 @@ +- **A `v*` tag no longer fails the whole release run with `startup_failure` + before any job starts.** `release.yml`'s `python-gates` called + `parity.yml` granting only `permissions: {contents: read}`, but + `parity.yml`'s `notify-planner-drift` job statically declares + `permissions: {issues: write}`. A called workflow may never exceed its + caller's grant, and GitHub evaluates that BEFORE any job runs -- regardless + of the `if:` that keeps `notify-planner-drift` to `repository_dispatch`, so + a tag build that never reaches the job still failed on it. The v0.6.0-rc1 + tag reproduced it exactly: run `31830969596`, `startup_failure`, zero jobs, + no release object and no assets. `python-gates` now grants `issues: write` + alongside `contents: read`; a sweep of every local caller/callee pair + reports zero remaining conflicts.