Files
computer-playbook/.github/workflows/entry-pull-request-change.yml
T
kevinveenbirkenbachandClaude Opus 5 effbd839c9 fix(ci): grant the orchestrator call the issues scope it requests
Every pull_request run of this workflow has ended in startup_failure - ten of
ten across feature/swarm-nfs, feature/svc-net-tor and feature/ai-agent-employees,
including runs that predate the current work. No jobs, no logs, no check runs,
because run-graph validation rejects the workflow before any job is created:

  entry-pull-request-change.yml (Line: 130, Col: 3): Error calling workflow
  'ci-orchestrator.yml'. The workflow is requesting 'issues: write', but is only
  allowed 'issues: none'.

ci-orchestrator.yml requests issues:write at workflow level (:69) and on its
report-main-failures job (:283). The caller declares an explicit permissions
block listing only actions/contents/packages/security-events, and an explicit
block pins every unlisted scope to none. A called workflow may request only a
subset of its caller's declared set, so the graph is invalid. Introduced by
8b58b67121, a three-caller fan-out where only two callers were patched.

The grant goes on the job rather than at workflow level. Job permissions REPLACE
the workflow block instead of merging with it, so the four existing scopes are
restated verbatim alongside the new one.

Do not move this block to workflow level. That block also governs the
pull_request_target half of this file, whose build-ci-images-fork and
images-mirror-missing-fork jobs check out fork-controlled refs with
allow_unsafe_checkout, one of them with secrets: inherit. Widening that token is
the only way this change becomes a security problem.

The scope itself carries no new capability on this path: report-main-failures is
gated on github.ref == refs/heads/main and is unreachable from a PR ref, forks
get a read-only token for pull_request regardless of the declared ceiling, and
same-repo PR authors already hold issue rights. The block already granted
actions:write and packages:write, both broader than issues:write.

Not provable locally - act does not evaluate nested permission escalation, so
this needs a push to confirm.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 02:13:18 +02:00

192 lines
6.6 KiB
YAML

name: "⚡ CI: Pull Request"
run-name: >-
${{ github.event_name }} / PR #${{ github.event.pull_request.number }} / ${{ github.event.action }} / ${{ github.event.pull_request.head.label }}
on:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
- labeled
pull_request_target:
types:
- opened
- synchronize
- reopened
- ready_for_review
- labeled
concurrency:
group: pr-ci-${{ github.repository }}-${{ github.event_name }}-pr-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
actions: write
contents: read
packages: write
security-events: write
jobs:
detect-pr-scope:
name: 🔎 Detect PR scope
if: ${{ github.event.pull_request.draft == false && (github.event_name == 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository) }}
runs-on: ubuntu-latest
outputs:
scope: ${{ steps.resolve.outputs.scope }}
run_ci_orchestrator: ${{ steps.resolve.outputs.run_ci_orchestrator }}
steps:
- name: ⬇️ Checkout
uses: actions/checkout@v7.0.0
- name: 🔍 Resolve PR scope
id: resolve
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPOSITORY: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: ./scripts/meta/resolve/pr/scope.sh
validate-pr-branch-prefix:
name: 🧭 Validate PR branch prefix
needs: detect-pr-scope
if: ${{ github.event_name == 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository }}
runs-on: ubuntu-latest
steps:
- name: ⬇️ Checkout
uses: actions/checkout@v7.0.0
- name: ✅ Validate branch prefix for detected scope
env:
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
PR_SCOPE: ${{ needs.detect-pr-scope.outputs.scope }}
run: ./scripts/meta/resolve/pr/branch_prefix.sh
assert-main-merged:
name: 🔀 Assert main is merged
needs: detect-pr-scope
if: ${{ github.event_name == 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository }}
runs-on: ubuntu-latest
steps:
- name: ⬇️ Checkout PR head
uses: actions/checkout@v7.0.0
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: ✅ Assert the base branch is fully merged into the PR head
env:
INFINITO_MAIN_REMOTE: origin
INFINITO_MAIN_BRANCH: ${{ github.event.pull_request.base.ref }}
run: bash scripts/git/assert/main_merged.sh
pr-scope-gate:
name: 🚦 Gate PR scope
needs:
- detect-pr-scope
- validate-pr-branch-prefix
if: ${{ needs.detect-pr-scope.outputs.run_ci_orchestrator == 'false' }}
runs-on: ubuntu-latest
steps:
- name: ✅ Confirm lightweight PR scope
shell: bash
run: |
set -euo pipefail
echo "Lightweight PR scope '${{ needs.detect-pr-scope.outputs.scope }}' passed."
detect-affected-roles:
name: 🧭 Detect affected roles
needs: detect-pr-scope
if: ${{ needs.detect-pr-scope.outputs.run_ci_orchestrator == 'true' }}
runs-on: ubuntu-latest
outputs:
whitelist: ${{ steps.resolve.outputs.whitelist }}
steps:
- name: ⬇️ Checkout
uses: actions/checkout@v7.0.0
with:
fetch-depth: 0
- name: 🔧 Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
cache: pip
cache-dependency-path: pyproject.toml
- name: 📦 Install project (host venv)
shell: bash
run: make install
- name: 🔍 Resolve affected roles
id: resolve
shell: bash
env:
SUBSET_LABELED: ${{ contains(github.event.pull_request.labels.*.name, '🧩 Subset') }}
PR_BODY: ${{ github.event.pull_request.body }}
run: bash scripts/github/resolve/pr_affected_roles.sh
ci-orchestrator:
needs:
- detect-pr-scope
- validate-pr-branch-prefix
- detect-affected-roles
if: ${{ github.event_name == 'pull_request' && needs.detect-pr-scope.outputs.run_ci_orchestrator == 'true' }}
permissions:
actions: write
contents: read
packages: write
security-events: write
issues: write
uses: ./.github/workflows/ci-orchestrator.yml
with:
distros: "arch debian ubuntu fedora centos"
whitelist: ${{ needs.detect-affected-roles.outputs.whitelist }}
trusted_pr: ${{ contains(github.event.pull_request.labels.*.name, '🛡️ Trusted') }}
prepare-fork-merge-ref:
needs:
- detect-pr-scope
- validate-pr-branch-prefix
if: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository && needs.detect-pr-scope.outputs.run_ci_orchestrator == 'true' }}
runs-on: ubuntu-latest
outputs:
checkout_ref: ${{ steps.out.outputs.checkout_ref }}
image_tag: ${{ steps.out.outputs.image_tag }}
trusted: ${{ contains(github.event.pull_request.labels.*.name, '🛡️ Trusted') }}
steps:
- name: ⬇️ Checkout
uses: actions/checkout@v7.0.0
- name: 🔍 Resolve merge ref and image tag
id: out
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha || '' }}
run: ./scripts/meta/resolve/pr/merge_ref.sh
build-ci-images-fork:
needs: prepare-fork-merge-ref
uses: ./.github/workflows/images-build-ci.yml
with:
distros: "arch debian ubuntu fedora centos"
checkout_ref: ${{ needs.prepare-fork-merge-ref.outputs.trusted == 'true' && github.event.pull_request.head.sha || needs.prepare-fork-merge-ref.outputs.checkout_ref }}
source_repository: ${{ needs.prepare-fork-merge-ref.outputs.trusted == 'true' && github.event.pull_request.head.repo.full_name || github.repository }}
image_tag: ${{ needs.prepare-fork-merge-ref.outputs.image_tag }}
concurrency_channel: pr-target-build-producer
allow_unsafe_checkout: true
images-mirror-missing-fork:
needs: prepare-fork-merge-ref
uses: ./.github/workflows/images-mirror-missing.yml
with:
repo_root: _pr_source
source_repository: ${{ github.repository }}
source_ref: ${{ needs.prepare-fork-merge-ref.outputs.checkout_ref }}
source_path: _pr_source
concurrency_channel: pr-target-mirror-producer
concurrency_key: pr-${{ github.event.pull_request.number }}
allow_unsafe_checkout: true
secrets: inherit