From 8113d287e8171944c760d04625a590f120281a8b Mon Sep 17 00:00:00 2001 From: James Toussaint <33313130+james-toussaint@users.noreply.github.com> Date: Fri, 23 Oct 2026 11:36:55 +0200 Subject: [PATCH 1/3] Add minimize-pragma workflow --- .github/workflows/minimize-pragma.yml | 74 +++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 .github/workflows/minimize-pragma.yml diff --git a/.github/workflows/minimize-pragma.yml b/.github/workflows/minimize-pragma.yml new file mode 100644 index 00000000000..b5b9f1f87c1 --- /dev/null +++ b/.github/workflows/minimize-pragma.yml @@ -0,0 +1,74 @@ +# Keeps Solidity pragma floors minimal (`npm run pragma`), opening a PR when any can be lowered. +# +# SETUP (one-time, org admin): a short-lived GitHub App token is minted so the opened PR triggers CI +# (the default GITHUB_TOKEN can't — events it creates don't start workflow runs). Create a GitHub App +# with Contents + Pull requests set to Read-and-write, install it on this repo, and add two secrets: +# PRAGMA_BOT_APP_ID and PRAGMA_BOT_PRIVATE_KEY. Reuse an existing org CI-bot App if one exists. +name: minimize-pragma + +on: + schedule: + - cron: '0 6 * * 1' # Mondays 06:00 UTC + workflow_dispatch: {} + +concurrency: + group: minimize-pragma + cancel-in-progress: false + +permissions: + contents: read # checkout only; the App token pushes the branch and opens the PR + +env: + NODE_OPTIONS: --max_old_space_size=8192 + +jobs: + minimize-pragma: + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' || github.repository == 'OpenZeppelin/openzeppelin-contracts' + steps: + - name: Generate app token + id: app-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.PRAGMA_BOT_APP_ID }} + private-key: ${{ secrets.PRAGMA_BOT_PRIVATE_KEY }} + + - uses: actions/checkout@v7 + with: + submodules: recursive # foundry libs, needed by the pragma tooling + token: ${{ steps.app-token.outputs.token }} # push authenticated as the App -> PR triggers CI + - name: Set up environment + uses: ./.github/actions/setup + with: + foundry: 'on' # needed by the pragma scripts (see checks.yml) + + - name: Minimize pragmas + run: npm run pragma + + - name: Detect changes + id: diff + run: git diff --quiet -- contracts || echo "changed=true" >> "$GITHUB_OUTPUT" + + - name: Open or update PR + if: steps.diff.outputs.changed == 'true' + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} # App token: opens the PR and lets its CI run + BRANCH: automation/minimize-pragma + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + run: | + # Attribute the commit to the App's bot identity (the account that pushes it). + uid="$(gh api "/users/${APP_SLUG}[bot]" --jq .id)" + git config user.name "${APP_SLUG}[bot]" + git config user.email "${uid}+${APP_SLUG}[bot]@users.noreply.github.com" + git switch -c "$BRANCH" + git add contracts + git commit -m "chore: minimize Solidity pragmas" + git push --force origin "$BRANCH" + if [ -n "$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number')" ]; then + echo "PR already open for $BRANCH; the force-push updated it." + else + gh pr create --base master --head "$BRANCH" \ + --label ignore-changeset \ + --title "chore: minimize Solidity pragmas" \ + --body "Automated \`npm run pragma\`: lowers pragma floors to the lowest compiler that still builds each file." + fi From 2320fc12d0eea595f83ceb8314c9c2c54ac06037 Mon Sep 17 00:00:00 2001 From: James Toussaint <33313130+james-toussaint@users.noreply.github.com> Date: Fri, 23 Oct 2026 12:32:29 +0200 Subject: [PATCH 2/3] Add review fixes --- .github/workflows/minimize-pragma.yml | 38 +++++++++++++++++---------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/.github/workflows/minimize-pragma.yml b/.github/workflows/minimize-pragma.yml index b5b9f1f87c1..b187fdf71b6 100644 --- a/.github/workflows/minimize-pragma.yml +++ b/.github/workflows/minimize-pragma.yml @@ -20,23 +20,20 @@ permissions: env: NODE_OPTIONS: --max_old_space_size=8192 + DEFAULT_BRANCH: master jobs: minimize-pragma: runs-on: ubuntu-latest if: github.event_name == 'workflow_dispatch' || github.repository == 'OpenZeppelin/openzeppelin-contracts' steps: - - name: Generate app token - id: app-token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ secrets.PRAGMA_BOT_APP_ID }} - private-key: ${{ secrets.PRAGMA_BOT_PRIVATE_KEY }} - - uses: actions/checkout@v7 with: + ref: ${{ env.DEFAULT_BRANCH }} submodules: recursive # foundry libs, needed by the pragma tooling - token: ${{ steps.app-token.outputs.token }} # push authenticated as the App -> PR triggers CI + # Read-only checkout: don't persist the token. The write-capable App token is minted after + # the build (below), so nothing usable sits in .git/config while untrusted deps run. + persist-credentials: false - name: Set up environment uses: ./.github/actions/setup with: @@ -49,25 +46,38 @@ jobs: id: diff run: git diff --quiet -- contracts || echo "changed=true" >> "$GITHUB_OUTPUT" + # Minted after the build and only when there's a PR to open, so build deps never see it. + # Scoped to the two permissions needed, not everything the (possibly shared) App installation grants. + - name: Generate app token + if: steps.diff.outputs.changed == 'true' + id: app-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.PRAGMA_BOT_APP_ID }} + private-key: ${{ secrets.PRAGMA_BOT_PRIVATE_KEY }} + permission-contents: write # push the automation branch + permission-pull-requests: write # open/update the PR + - name: Open or update PR if: steps.diff.outputs.changed == 'true' env: GH_TOKEN: ${{ steps.app-token.outputs.token }} # App token: opens the PR and lets its CI run - BRANCH: automation/minimize-pragma APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + BRANCH: automation/minimize-pragma + BASE: ${{ env.DEFAULT_BRANCH }} run: | - # Attribute the commit to the App's bot identity (the account that pushes it). uid="$(gh api "/users/${APP_SLUG}[bot]" --jq .id)" git config user.name "${APP_SLUG}[bot]" git config user.email "${uid}+${APP_SLUG}[bot]@users.noreply.github.com" git switch -c "$BRANCH" git add contracts git commit -m "chore: minimize Solidity pragmas" - git push --force origin "$BRANCH" - if [ -n "$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number')" ]; then - echo "PR already open for $BRANCH; the force-push updated it." + # Auth via the token in the remote URL, not persisted git credentials. + git push --force "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "$BRANCH" + if [ -n "$(gh pr list --head "$BRANCH" --base "$BASE" --state open --json number --jq '.[0].number')" ]; then + echo "PR already open for $BRANCH -> $BASE; the force-push updated it." else - gh pr create --base master --head "$BRANCH" \ + gh pr create --base "$BASE" --head "$BRANCH" \ --label ignore-changeset \ --title "chore: minimize Solidity pragmas" \ --body "Automated \`npm run pragma\`: lowers pragma floors to the lowest compiler that still builds each file." From 040f71896e13e6d60bc2b83c36883fdd9e7da534 Mon Sep 17 00:00:00 2001 From: Hadrien Croubois Date: Wed, 28 Oct 2026 12:06:27 +0100 Subject: [PATCH 3/3] Use step outcome instead of output to detect pragma changes --- .github/workflows/minimize-pragma.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/minimize-pragma.yml b/.github/workflows/minimize-pragma.yml index b187fdf71b6..715a134a457 100644 --- a/.github/workflows/minimize-pragma.yml +++ b/.github/workflows/minimize-pragma.yml @@ -44,12 +44,13 @@ jobs: - name: Detect changes id: diff - run: git diff --quiet -- contracts || echo "changed=true" >> "$GITHUB_OUTPUT" + continue-on-error: true # exits 1 when pragmas changed + run: git diff --quiet -- contracts # Minted after the build and only when there's a PR to open, so build deps never see it. # Scoped to the two permissions needed, not everything the (possibly shared) App installation grants. - name: Generate app token - if: steps.diff.outputs.changed == 'true' + if: steps.diff.outcome == 'failure' id: app-token uses: actions/create-github-app-token@v2 with: @@ -59,7 +60,7 @@ jobs: permission-pull-requests: write # open/update the PR - name: Open or update PR - if: steps.diff.outputs.changed == 'true' + if: steps.diff.outcome == 'failure' env: GH_TOKEN: ${{ steps.app-token.outputs.token }} # App token: opens the PR and lets its CI run APP_SLUG: ${{ steps.app-token.outputs.app-slug }}