Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions .github/workflows/dev-version-bump.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,35 @@ name: Dev version bump
# sign-off that a bot cannot supply. Until that merge the red persists. This converts a
# forgotten chore into a queued, reviewable change - not into an automatic repair.
#
# A `release` event resolves this workflow file from the repository DEFAULT branch
# (`main`), not from `dev` - the same trap documented in cleanup-closed-pr-branches.yml.
# So merging this file to `dev` installs it but arms nothing; it first fires after an
# ordinary dev -> main promotion carries it there.
# WHY THIS IS CALLED, NOT TRIGGERED. It used to listen for `release: published`, and in
# that form it ran ZERO times across v2.37.0, v2.38.0 and v2.39.0 - every one of those
# bumps was still opened by hand (#3045, #3076, #3127). The workflow was not broken; the
# event never existed. `release.yml` creates the GitHub release with
# `GH_TOKEN: ${{ github.token }}`, and GitHub does not start workflow runs from events
# raised by the default `GITHUB_TOKEN`. A `release: published` listener therefore cannot
# observe a release this repository publishes itself, no matter which branch it sits on.
#
# The fix keeps the credential surface unchanged: no PAT, no app token, no
# `contents: write` on the release job. `release.yml` CALLS this workflow directly after
# a successful publish, so the run is a child of the release run instead of a reaction to
# an event that is never delivered.
#
# A `workflow_call` body resolves from the CALLER's ref, and `release.yml` only ever runs
# on `main` or `preview` (its own branch gate). So this file must be on `main` to take
# effect - the same promotion requirement the old comment described, now for a different
# reason.
#
# There is deliberately no `workflow_dispatch`: a branch-selected manual run executes
# THAT branch body with `contents: write`. Re-drive a missed run by running
# `bun scripts/bump-dev-version.ts <released> package.json` locally and opening the pull
# request normally.
on:
release:
types: [published]
workflow_call:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Trigger checks for the bot-created pull request

After the caller's permissions are fixed, every bump that changes dev still pushes the branch and creates its PR with the default github.token. GitHub suppresses workflow runs for events generated by that token—the same behavior motivating this change—so neither the branch push nor PR creation starts ci.yml or enforce-pr-target.yml; the automated PR will consequently lack its required checks until a human retriggers an event. Create it with an approved non-default credential or explicitly arrange a non-suppressed trigger for the required PR workflows.

AGENTS.md reference: .github/AGENTS.md:L16-L18

Useful? React with 👍 / 👎.

inputs:
released-version:
description: "The tag that just published, e.g. v2.39.0"
required: true
type: string

permissions: {}

Expand Down Expand Up @@ -69,7 +86,7 @@ jobs:
- name: Decide the version dev should carry
id: decide
env:
RELEASED_VERSION: ${{ github.event.release.tag_name }}
RELEASED_VERSION: ${{ inputs.released-version }}
run: |
set -euo pipefail
bun scripts/bump-dev-version.ts "${RELEASED_VERSION}" package.json
Expand All @@ -88,7 +105,7 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
NEXT_VERSION: ${{ steps.decide.outputs.version }}
RELEASED_VERSION: ${{ github.event.release.tag_name }}
RELEASED_VERSION: ${{ inputs.released-version }}
run: |
set -euo pipefail

Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,41 @@ concurrency:
cancel-in-progress: false

jobs:
# Move `dev` past the version that just published.
#
# This is a CALL, not a `release: published` listener. The release is created with
# `github.token`, and GitHub does not start workflow runs from events that token
# raises - so a listener cannot observe a release this repository publishes itself. In
# that form it ran ZERO times across v2.37.0, v2.38.0 and v2.39.0 while every one of
# those bumps was opened by hand (#3045, #3076, #3127).
#
# `needs: publish` means this is skipped unless the publish job succeeded, so a failed
# publish or a failed release creation never opens a bump pull request; the explicit
# condition only adds the dry-run case. The called workflow declares its own
# `contents: write` / `pull-requests: write` for its own job, so nothing here gains
# write access.
#
# Both channels call this, and the double-call is safe because `bump-dev-version.ts`
# compares against what `dev` already carries. In the usual train `dev` is already at
# the stable core when the preview publishes, so that call returns `changed=false`
# ("dev already carries 2.40.0, which is ahead of the published 2.40.0-preview.*") and
# every later step is gated on that output. The stable call returns `changed=true` and
# opens the one pull request. A preview publishing while `dev` is genuinely behind
# still bumps it, which is the point.
#
# It is declared FIRST in this file, ahead of the jobs it depends on, because
# tests/ci-workflows.test.ts splits the workflow on `- name:` and reads each `run:`
# block to the start of the next one when it checks that dispatch inputs never
# interpolate into shell source. A job declared between two steps lands inside that
# window and reads as shell. Job order in YAML carries no execution meaning - `needs`
# does - so declaring it before its own dependency costs nothing.
bump-dev-version:
needs: publish
if: ${{ inputs.dry-run != true }}
uses: ./.github/workflows/dev-version-bump.yml

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Grant the reusable call its required write scopes

On every successful non-dry-run release, this call inherits the workflow-level permissions: {}, while GitHub only permits a called reusable workflow to maintain or reduce the caller's token permissions. The called job therefore cannot elevate itself to contents: write or pull-requests: write, so the push or PR creation will be rejected and the intended version-bump PR will not open. Grant those two scopes on this calling job; they will remain isolated from publish.

AGENTS.md reference: .github/AGENTS.md:L16-L16

Useful? React with 👍 / 👎.

with:
released-version: v${{ inputs.version }}

validate-dispatch:
runs-on: ubuntu-latest
permissions:
Expand Down
Loading