From d0f8c1d65ce283fe8ac8584479c08719f33dfac6 Mon Sep 17 00:00:00 2001 From: peptiderepo Date: Tue, 16 Jun 2026 11:44:24 +0000 Subject: [PATCH] =?UTF-8?q?fix(ci):=20permissions=20model=20=E2=80=94=20no?= =?UTF-8?q?=20top-level=20perms=20in=20ci.yml;=20callers=20need=20write?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub Actions permission model for reusable workflows: callers must declare at least the permission level that any job in the called workflow requests. Since the phpcs job declares 'permissions: contents: write' (for phpcbf commit-back), all callers must include 'permissions: contents: write' at their workflow level. Changes: - ci.yml: remove top-level permissions block (was: contents: read); phpcs job already has job-level contents: write. Docblock now explains the caller requirement clearly. - ci-self-test.yml: add 'permissions: contents: write' at workflow level. - README.md: adoption snippet now includes the required permissions block. - Remove diagnostic bisect/minimal test workflows (used to identify the root cause of startup_failure; finding: job-level write in reusable workflow requires caller to declare write at workflow level). Confirmed: ci-bisect-caller with permissions: contents: write = SUCCESS. Confirmed: actionlint 1.7.4 + yamllint 1.38.0 both clean. Agent-Session: claude/reusable-ci-20260616 --- .github/workflows/ci-bisect-caller.yml | 15 -------------- .github/workflows/ci-bisect.yml | 27 -------------------------- .github/workflows/ci-minimal-test.yml | 12 ------------ .github/workflows/ci-minimal.yml | 18 ----------------- .github/workflows/ci-self-test.yml | 10 +++++++--- .github/workflows/ci.yml | 11 +++++------ README.md | 6 ++++++ 7 files changed, 18 insertions(+), 81 deletions(-) delete mode 100644 .github/workflows/ci-bisect-caller.yml delete mode 100644 .github/workflows/ci-bisect.yml delete mode 100644 .github/workflows/ci-minimal-test.yml delete mode 100644 .github/workflows/ci-minimal.yml diff --git a/.github/workflows/ci-bisect-caller.yml b/.github/workflows/ci-bisect-caller.yml deleted file mode 100644 index 04d7f06..0000000 --- a/.github/workflows/ci-bisect-caller.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: CI Bisect Caller v3 - -on: - push: - branches: ['claude/**'] - -permissions: - contents: write # needed for called reusable workflow's phpcs job - -jobs: - ci: - uses: ./.github/workflows/ci-bisect.yml - with: - tests: none - secrets: inherit diff --git a/.github/workflows/ci-bisect.yml b/.github/workflows/ci-bisect.yml deleted file mode 100644 index 010f147..0000000 --- a/.github/workflows/ci-bisect.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: CI Bisect Test v3 - -on: - workflow_call: - inputs: - tests: - type: string - required: false - default: stubs - -# No top-level permissions — only job-level where needed - -jobs: - phpcs: - name: PHPCS - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - run: echo "phpcs with write" - - phpunit: - name: PHPUnit - runs-on: ubuntu-latest - if: ${{ inputs.tests != 'none' }} - steps: - - run: echo "phpunit ${{ inputs.tests }}" diff --git a/.github/workflows/ci-minimal-test.yml b/.github/workflows/ci-minimal-test.yml deleted file mode 100644 index c1446c7..0000000 --- a/.github/workflows/ci-minimal-test.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Minimal Reusable Test - -on: - push: - branches: ['claude/**'] - -jobs: - test: - uses: ./.github/workflows/ci-minimal.yml - with: - test_input: hello - secrets: inherit diff --git a/.github/workflows/ci-minimal.yml b/.github/workflows/ci-minimal.yml deleted file mode 100644 index d347a8f..0000000 --- a/.github/workflows/ci-minimal.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Minimal Reusable - -on: - workflow_call: - inputs: - test_input: - type: string - required: false - default: world - -permissions: - contents: read - -jobs: - hello: - runs-on: ubuntu-latest - steps: - - run: echo "Hello ${{ inputs.test_input }}" diff --git a/.github/workflows/ci-self-test.yml b/.github/workflows/ci-self-test.yml index 4cb4e7c..b1a0c75 100644 --- a/.github/workflows/ci-self-test.yml +++ b/.github/workflows/ci-self-test.yml @@ -1,8 +1,9 @@ # Self-test: exercises the reusable ci.yml on this repo itself. # Uses tests: none (peptide-e2e is a Playwright/Node repo, no PHP suite). -# phpcs_autofix: false avoids needing contents: write at the caller level -# (the phpcs job in ci.yml has its own job-level write permission for phpcbf). -# Purpose: smoke-check that the reusable workflow skeleton runs without errors. +# permissions: contents: write is required at the caller level — any caller of +# ci.yml must declare this so the phpcs job can commit phpcbf fixes back to the +# PR branch. Even with phpcs_autofix: false here, the permission must be declared +# for the reusable workflow to pass GH's startup permission-escalation check. name: CI Self-Test (reusable ci.yml) @@ -12,6 +13,9 @@ on: pull_request: branches: [main] +permissions: + contents: write # required: called ci.yml phpcs job declares job-level write for phpcbf + jobs: ci: uses: ./.github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac0880d..d286917 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,9 +50,11 @@ # and concatenated verbatim — missing slash produces wrong path). # - Every repo calls `composer test` regardless of the `tests` input; the # input only controls environment provisioning, not the command. -# - permissions: contents: read at workflow level; the phpcs job sets -# contents: write for phpcbf commit-back only. Calling workflows do NOT -# need to declare contents: write — job-level override is sufficient. +# - permissions: the phpcs job declares `contents: write` at job level for +# phpcbf commit-back. GitHub requires the CALLING WORKFLOW to also declare +# `permissions: contents: write` at the caller's workflow level — GH enforces +# that callers cannot be escalated beyond their declared scope, even by job-level +# overrides in the reusable workflow. All caller repos must include this. ## name: CI — Reusable @@ -100,9 +102,6 @@ on: default: true type: boolean -permissions: - contents: read # jobs that need write declare it explicitly (phpcs) - jobs: # --------------------------------------------------------------------------- diff --git a/README.md b/README.md index 9eb9fab..7643b3b 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,12 @@ on: pull_request: branches: [main] +# Required: the called ci.yml phpcs job declares contents: write for phpcbf +# commit-back. GitHub enforces that callers must grant at least the permission +# level the reusable workflow's jobs request. +permissions: + contents: write + jobs: ci: uses: peptiderepo/peptide-e2e/.github/workflows/ci.yml@main