diff --git a/.github/workflows/auto-revert-on-main-failure.yml b/.github/workflows/auto-revert-on-main-failure.yml index c1708c6..96b1ba4 100644 --- a/.github/workflows/auto-revert-on-main-failure.yml +++ b/.github/workflows/auto-revert-on-main-failure.yml @@ -34,6 +34,33 @@ on: description: "Runner label(s) as a JSON array string (parsed with fromJSON). Pass '[\"self-hosted\", \"linux\", \"x64\"]' to use the self-hosted pool." type: string default: '["ubuntu-latest"]' + revert-app-client-id: + description: >- + Optional. Client ID of a GitHub App whose installation has + Workflows: write, used to push the revert branch. + + GITHUB_TOKEN — the default — is issued by GitHub's own Actions App and + is categorically forbidden from creating or updating any file under + .github/workflows/. There is no `permissions:` key that grants it; + pushing a revert of a commit that touched a workflow file fails with + "refusing to allow a GitHub App to create or update workflow ... + without `workflows` permission". So without this input a workflow-file + regression cannot be reverted automatically. The job opens an issue + instead of failing (see below). + + Set this, with the `REVERT_APP_PRIVATE_KEY` secret, to revert those + commits too. Use a dedicated App: a token that can rewrite workflow + files can rewrite CI itself, so it wants its own key and its own + installation rather than sharing one with deps-reader. + type: string + default: "" + secrets: + REVERT_APP_PRIVATE_KEY: + description: >- + Private key of the App named by `revert-app-client-id`. Required only + when that input is set; without it the job falls back to GITHUB_TOKEN + and opens an issue for workflow-file regressions instead of reverting. + required: false jobs: open-revert-pr: @@ -42,16 +69,32 @@ jobs: permissions: contents: write pull-requests: write + # issues: write is the fallback path — when the bad commit touched a + # workflow file and no App is configured, the job opens an issue rather + # than failing on a push GITHUB_TOKEN is never allowed to make. + issues: write steps: + - name: Mint revert token + id: revert-token + if: inputs.revert-app-client-id != '' + uses: nkg/github-actions/.github/actions/setup-token@v3 + with: + app-client-id: ${{ inputs.revert-app-client-id }} + app-private-key: ${{ secrets.REVERT_APP_PRIVATE_KEY }} + permission-contents: write + permission-pull-requests: write + permission-workflows: write + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 ref: ${{ inputs.default-branch }} - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ steps.revert-token.outputs.token || secrets.GITHUB_TOKEN }} - name: Open revert PR env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.revert-token.outputs.token || secrets.GITHUB_TOKEN }} + HAVE_WORKFLOW_SCOPE: ${{ inputs.revert-app-client-id != '' }} BAD_SHA: ${{ inputs.bad-sha }} FAILED_RUN_URL: ${{ inputs.failed-run-url }} DEFAULT_BRANCH: ${{ inputs.default-branch }} @@ -75,6 +118,48 @@ jobs: short="${BAD_SHA:0:7}" branch="auto-revert/${short}" + # GITHUB_TOKEN cannot push a commit that touches .github/workflows/, + # ever — it is a platform rule, not a `permissions:` we forgot to + # grant. Detect it here rather than letting `git push` fail after the + # revert commit is already made: that produced a red check whose log + # said "refusing to allow a GitHub App to create or update workflow", + # which reads like a misconfiguration and buries the actual CI + # failure it was reacting to. + # + # Open an issue instead, so the regression is still surfaced loudly. + # Configure revert-app-client-id to revert these automatically. + # `diff-tree -r ` lists nothing for a merge commit, and the bad + # SHA is usually a merge — diffing against the first parent covers + # both shapes. + changed=$(git diff --name-only "${BAD_SHA}^1" "$BAD_SHA") + + if [[ "$HAVE_WORKFLOW_SCOPE" != "true" ]] \ + && grep -q '^\.github/workflows/' <<<"$changed"; then + echo "$short touches .github/workflows/ and no revert App is configured." + title="main is red at ${short} and cannot be auto-reverted" + if gh issue list --search "$title in:title" --state open --json number --jq 'length' | grep -q '^[1-9]'; then + echo "issue already open — skipping" + exit 0 + fi + cat >/tmp/revert-issue.md <` lists nothing for a merge + commit and the bad SHA usually *is* a merge; and auto-revert has therefore + never once succeeded against a workflow-file regression. + +### Added + +- **`auto-revert-on-main-failure.yml` gained `revert-app-client-id` and a + `REVERT_APP_PRIVATE_KEY` secret**, both optional. When supplied, the revert + branch is pushed with a scoped App installation token carrying + `permission-workflows: write`, so workflow-file commits are reverted + automatically rather than raising an issue. Use a dedicated App: a credential + that can rewrite workflow files can rewrite CI itself. + ## [3.5.0] - 2026-09-18 ### Added diff --git a/examples/README.md b/examples/README.md index a027114..b9436ee 100644 --- a/examples/README.md +++ b/examples/README.md @@ -722,12 +722,41 @@ jobs: permissions: contents: write pull-requests: write + issues: write uses: nkg/github-actions/.github/workflows/auto-revert-on-main-failure.yml@v3 with: bad-sha: ${{ github.event.workflow_run.head_sha }} failed-run-url: ${{ github.event.workflow_run.html_url }} ``` +**Commits that touch `.github/workflows/` cannot be auto-reverted by default.** +The job pushes with `GITHUB_TOKEN`, which GitHub categorically forbids from +creating or updating workflow files — there is no `permissions:` key that grants +it. Left alone, the push is rejected *after* the revert commit is made, and the +job fails with `refusing to allow a GitHub App to create or update workflow ... +without 'workflows' permission`, which reads like a misconfiguration and buries +the CI failure it was reacting to. + +Instead, the job detects that case up front and **opens an issue** rather than +failing, so the regression is still surfaced. That is why the stub grants +`issues: write`. + +To revert those commits automatically too, supply a GitHub App installation that +has **Workflows: read and write**: + +```yaml + with: + bad-sha: ${{ github.event.workflow_run.head_sha }} + failed-run-url: ${{ github.event.workflow_run.html_url }} + revert-app-client-id: Iv23li... # public, from the App's page + secrets: + REVERT_APP_PRIVATE_KEY: ${{ secrets.REVERT_APP_PRIVATE_KEY }} +``` + +Use a **dedicated** App for this, not one you already use for something else. +A token that may rewrite workflow files can rewrite CI itself, so it wants its +own key, its own installation, and only the repos that need it. + **Client-side complement:** `auto-revert` reacts *after* bad code lands. Pair it with a `pre-push` git hook that runs the same checks CI runs, so broken code never reaches `origin` in the first place. See