From 89833e00e44006b6af80c994ed9f123642bb7649 Mon Sep 17 00:00:00 2001 From: Titus Kirch Date: Tue, 1 Sep 2026 21:30:43 +0200 Subject: [PATCH 1/2] feat(ci): call the central workflow bodies instead of copying them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 649 lines of workflow become 101 of trigger. This repo is the estate's main-only case, and it is what the derivation was built for: none of the fast-forward triggers carries a target branch, so the body asks whether a dev branch exists and lands on main because there is none. Nothing in these files states that — the copies carried INTEGRATION_BRANCH: main as a literal that had to be kept in step with the trigger by hand. The gate picks up pnpm validate from package.json without being told it exists. The analysis now runs once per change rather than twice, and a prose-only change skips the gate. --- .github/workflows/ci.yml | 45 +-- .github/workflows/codeql.yml | 34 +-- .github/workflows/fast-forward-queue.yml | 362 +---------------------- .github/workflows/queue-branch.yml | 169 +---------- 4 files changed, 31 insertions(+), 579 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5959c94..9ad6ad8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,7 @@ # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +# The checks come from package.json's `check` script — which here ends in +# `pnpm validate`, a repo-specific step the body picks up without being told. name: CI on: @@ -7,44 +9,15 @@ on: branches: - main types: [opened, synchronize, reopened, ready_for_review] - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + paths-ignore: + - '**.md' + - 'docs/**' + - LICENSE permissions: contents: read - pull-requests: write jobs: - lint: - name: Lint & Format - runs-on: ubuntu-latest - if: github.event.pull_request.draft == false - steps: - - uses: actions/checkout@v7 - - - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6 - - - uses: actions/setup-node@v7 - with: - node-version: 24 - cache: pnpm - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Lint - run: pnpm lint - - - name: Format check - run: pnpm format - - - name: Typecheck - run: pnpm typecheck - - # The gate the entries actually need: the schema plus the cross-file - # checks it cannot express. Without it a mistyped category or technology - # drops a project off the page, silently. - - name: Validate entries - run: pnpm validate + check: + name: CI + uses: kirchDev/workflows/.github/workflows/_ci-check.yml@f47f598f549a3c97924fdfd00f15371ea557e1aa # v0.1.1 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ec89cb9..fe949d3 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -7,43 +7,17 @@ on: branches: [main] pull_request: branches: [main] + types: [opened, synchronize, reopened, ready_for_review] schedule: - cron: '34 23 * * 0' workflow_dispatch: -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - jobs: - analyze: - name: Analyze (${{ matrix.language }}) - runs-on: ubuntu-latest - timeout-minutes: 20 + codeql: + name: CodeQL permissions: security-events: write packages: read actions: read contents: read - - strategy: - fail-fast: false - matrix: - language: [actions, javascript-typescript] - - steps: - - uses: actions/checkout@v7 - with: - fetch-depth: 1 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v4.37.9 - with: - languages: ${{ matrix.language }} - build-mode: none - queries: security-extended,security-and-quality - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4.37.9 - with: - category: '/language:${{ matrix.language }}' + uses: kirchDev/workflows/.github/workflows/_codeql.yml@f47f598f549a3c97924fdfd00f15371ea557e1aa # v0.1.1 diff --git a/.github/workflows/fast-forward-queue.yml b/.github/workflows/fast-forward-queue.yml index db85652..32f093b 100644 --- a/.github/workflows/fast-forward-queue.yml +++ b/.github/workflows/fast-forward-queue.yml @@ -1,47 +1,14 @@ # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +# None of these triggers carries a target branch, so the body resolves it by +# asking whether a `dev` branch exists. Here it does not, and the queue lands +# on `main` — with no value stated in this file. name: Fast-forward Queue -# Lands a queue branch on the integration branch by FAST-FORWARD, so every -# commit arrives individually with its message intact — release-please reads -# those messages to build one CHANGELOG entry per change, and a squash would -# collapse a whole drain into one. See ADR-0008 in kirchDev/infrastructure. -# -# THE HUMAN GATE IS AN APPROVAL ON THE QUEUE PR, and the App is what makes that -# possible. GitHub disables the approve button for a PR's own AUTHOR, so a queue -# PR opened by the sole maintainer could never be approved by them — which is -# why ADR-0008 reached for a `fast-forward-queue` environment instead, and why -# its amendment records that GitHub does not sell required reviewers for a -# private repo on Team. Here the App opens the PR, so the maintainer is not its -# author and can approve it; `pick` then refuses to land anything without that -# approval. See ADR-0010 in kirchDev/infrastructure. -# -# THE APPROVAL IS CHECKED HERE, NOT BY A RULESET. Two reasons, both structural: -# the App bypasses the merge-gate ruleset, so a `required_approving_review_count` -# there would be skipped along with everything else in it; and the ruleset sets -# that count to 0 anyway, which makes GitHub's own `reviewDecision` null rather -# than APPROVED however many approvals a PR carries. The rules the platform -# would have applied — an approver with write access, an approval that is not -# stale — are therefore spelled out in `pick` below. on: - # A queue branch was pushed. The App opens its PR if none is open yet; nothing - # lands on this trigger, because a fresh push has no CI behind it and no - # approval that could still be current. `push` runs the workflow file from the - # pushed branch, which is cut from the integration branch and so carries it. push: branches: - 'ai/queue-**' - # An approval arrived. This is the ONE automatic landing trigger, and it fires - # only from the DEFAULT branch's copy of this file, so it stays inert until the - # rollup lands this on `main`; until then, `workflow_dispatch` is the way. - # - # THE APPROVAL MUST COME LAST — that is a condition, not a style preference. - # An approval is only counted while its `commit_id` is still the head, so any - # push after it un-approves the PR; and nothing fires when CI goes green on an - # already-approved PR. `check_suite: completed` was meant to cover exactly that - # case and cannot: GitHub does not fire it "if the check suite was created by - # GitHub Actions", which is every suite this repo produces. Approve after CI is - # green, or land it with `workflow_dispatch`. See the ADR-0010 amendment in kirchDev/infrastructure. pull_request_review: types: [submitted] workflow_dispatch: @@ -51,325 +18,16 @@ on: required: false type: string -# One fast-forward at a time. Two concurrent runs would race on the same ref and -# the loser's update would stop being a fast-forward mid-flight. -concurrency: - group: fast-forward-queue - cancel-in-progress: false - -env: - # The repo's integration branch — first entry of its branch_flow. - INTEGRATION_BRANCH: main - # Only a head branch with this prefix may be fast-forwarded. A ruleset can - # condition on the TARGET ref only, so this check is the head-branch half of - # the App bypass and lives here on purpose (ADR-0008 in kirchDev/infrastructure). - QUEUE_BRANCH_PREFIX: ai/queue- - jobs: - # The App authors the queue PR. That is the whole point of this job: an - # approval is only possible on a PR the approver did not open, and the drain - # runs under the maintainer's identity. - # - # `github.event.deleted` guards the push that DELETES a queue branch — the ref - # is gone by then and there is nothing to open a PR from. - open: - name: Open the queue PR - if: github.event_name == 'push' && github.event.deleted == false - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - steps: - # Checked with the workflow token, so the App token is minted only when a - # PR actually has to be created — every later push to the same queue - # branch already has one and stops here. - - name: Is a queue PR already open? - id: existing - env: - GH_TOKEN: ${{ github.token }} - GH_REPO: ${{ github.repository }} - BRANCH: ${{ github.ref_name }} - run: | - set -euo pipefail - existing=$(gh pr list --state open --base "$INTEGRATION_BRANCH" --head "$BRANCH" \ - --json number --jq '.[0].number // empty') - if [ -n "$existing" ]; then - echo "PR #$existing is already open for $BRANCH — the push updated it." - echo "create=false" >> "$GITHUB_OUTPUT" - else - echo "No open PR for $BRANCH -> $INTEGRATION_BRANCH." - echo "create=true" >> "$GITHUB_OUTPUT" - fi - - # Same runtime-from-Bitwarden pattern as the landing job below; the PEM is - # never a GitHub secret. - - name: Fetch Queue App PEM from Bitwarden - if: steps.existing.outputs.create == 'true' - uses: bitwarden/sm-action@1238aae8fc64b212641190a9227c8a734ab1a793 # v3.0.1 - with: - access_token: ${{ secrets.BWS_ACCESS_TOKEN }} - cloud_region: eu - secrets: | - df8b447a-ffd5-4009-9cc3-b49b014f6978 > KIRCHDEV_QUEUE_APP_PEM - - - name: Mint Queue App token - id: app-token - if: steps.existing.outputs.create == 'true' - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 - with: - app-id: '4473889' - private-key: ${{ env.KIRCHDEV_QUEUE_APP_PEM }} - - # Opened READY, not draft: ci.yml skips draft PRs, and `pick` skips them - # too, so a draft queue PR would be a PR nothing ever runs on and nothing - # could ever land. The title is cosmetic — a fast-forward never turns it - # into a commit message — but it follows the repo's convention anyway. - - name: Open the queue PR as the App - if: steps.existing.outputs.create == 'true' - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - GH_REPO: ${{ github.repository }} - BRANCH: ${{ github.ref_name }} - run: | - set -euo pipefail - - # A quoted heredoc: the body is copied through verbatim, so its - # backticks and asterisks need no shell escaping. - # - # ONE PARAGRAPH PER LINE, however long the line runs. GitHub renders - # comment and PR bodies with hard line breaks on, unlike a .md file in - # the repo, so a hand-wrapped paragraph arrives wrapped at whatever - # column it was written to — a narrow column of text in a wide box, - # re-wrapped again by the reader's own viewport. - body=$(cat <<'EOF' - Queue branch for an AI drain. Every worker PR targets this branch, so CI runs once here instead of once per issue. - - **Approving this PR is what lands it.** Once every check is green and this PR carries an approval on its **current head**, `fast-forward-queue.yml` fast-forwards the integration branch onto it — each commit arriving individually, with its message intact, so release-please still sees one entry per change. Nothing lands without that approval, green CI included. - - A push after the approval invalidates it: the approval is checked against the head commit it was given on, because nothing else dismisses it here. - EOF - ) - - gh pr create \ - --base "$INTEGRATION_BRANCH" \ - --head "$BRANCH" \ - --title "chore(queue): land $BRANCH on $INTEGRATION_BRANCH" \ - --body "$body" - - # Re-checks every candidate against every condition below, so a wrong dispatch - # input, an unapproved PR or a red one ends the run green instead of landing - # something. - pick: - name: Pick the queue PR - if: github.event_name != 'push' - runs-on: ubuntu-latest + fast-forward-queue: + name: Fast-forward Queue permissions: contents: read pull-requests: read - # `gh pr checks` reads the statusCheckRollup, which spans check runs - # (checks) and commit statuses (statuses). A permissions block sets every - # scope it doesn't name to none, so omitting either fails this job. checks: read statuses: read - outputs: - pr: ${{ steps.pick.outputs.pr }} - sha: ${{ steps.pick.outputs.sha }} - steps: - # A dispatch may name exactly one PR; every other trigger (and a dispatch - # with an empty input) considers every open queue PR. Anything that is not - # an open, ready, green, APPROVED queue PR into the integration branch - # leaves `pr` empty and the job ends green. - - name: Pick the queue PR - id: pick - env: - GH_TOKEN: ${{ github.token }} - GH_REPO: ${{ github.repository }} - INPUT_PR: ${{ inputs.pr }} - run: | - set -euo pipefail - - if [ -n "${INPUT_PR:-}" ]; then - candidates=$INPUT_PR - else - candidates=$(gh pr list --state open --base "$INTEGRATION_BRANCH" \ - --json number,headRefName \ - --jq '.[] | select(.headRefName | startswith(env.QUEUE_BRANCH_PREFIX)) | .number') - fi - - if [ -z "$candidates" ]; then - echo "No open queue PR into $INTEGRATION_BRANCH — nothing to do." - exit 0 - fi - - for n in $candidates; do - pr=$(gh pr view "$n" --json number,state,isDraft,baseRefName,headRefName,headRefOid,isCrossRepository) - state=$(printf '%s' "$pr" | jq -r '.state') - draft=$(printf '%s' "$pr" | jq -r '.isDraft') - base=$(printf '%s' "$pr" | jq -r '.baseRefName') - head=$(printf '%s' "$pr" | jq -r '.headRefName') - sha=$(printf '%s' "$pr" | jq -r '.headRefOid') - fork=$(printf '%s' "$pr" | jq -r '.isCrossRepository') - - # The branch NAME alone is not the head-branch half of the bypass: a - # fork's branch can be called ai/queue-* just as well, and this - # pattern is meant to hold on a public repo too. Only a branch in - # this repository qualifies. - if [ "$fork" != "false" ]; then - echo "PR #$n: head is in a fork — skipping." - continue - fi - - case "$head" in - "$QUEUE_BRANCH_PREFIX"*) ;; - *) - echo "PR #$n: head '$head' is not a queue branch — skipping." - continue - ;; - esac - - # A draft PR is OPEN — `isDraft` is a separate field, and skipping it - # would land a queue branch its author has explicitly marked as not - # ready. Draft is the drain's way of saying "not yet". - if [ "$state" != "OPEN" ] || [ "$draft" != "false" ] || [ "$base" != "$INTEGRATION_BRANCH" ]; then - echo "PR #$n: state=$state draft=$draft base=$base — not a ready PR into $INTEGRATION_BRANCH, skipping." - continue - fi - - # Green means every check, not just the required ones: the queue PR - # is the ONLY gate the drain's commits ever pass through. - if ! gh pr checks "$n"; then - echo "PR #$n: checks are not (yet) all green — skipping." - continue - fi - - # THE HUMAN GATE. `reviewDecision` is deliberately not used: the - # merge-gate ruleset sets required_approving_review_count to 0, and - # GitHub reports reviewDecision as null wherever no review is - # required — however many approvals the PR actually carries. So the - # three things the platform would have checked are checked here: - # the latest review per author, the approval's freshness, and the - # approver's write access. - # - # --paginate emits one array per page, so `.[] | …` streams the - # reviews and `jq -s` collects them back into a single array. - reviews=$(gh api --paginate "repos/$GH_REPO/pulls/$n/reviews" \ - --jq '.[] | {login: .user.login, state: .state, commit: .commit_id}' | jq -s '.') - - # COMMENTED and DISMISSED reviews are dropped BEFORE the per-author - # reduction: a later comment must not clear an approval, and a - # dismissed review must not count as one. group_by is stable, so - # `last` is each author's most recent verdict. - latest=$(printf '%s' "$reviews" \ - | jq -c '[.[] | select(.state == "APPROVED" or .state == "CHANGES_REQUESTED")] - | group_by(.login) | map(last)') - - if [ "$(printf '%s' "$latest" | jq -r '[.[] | select(.state == "CHANGES_REQUESTED")] | length')" != "0" ]; then - echo "PR #$n: changes are requested — skipping." - continue - fi - - # An approval names the commit it was given on. Nothing dismisses a - # stale one here — dismiss_stale_reviews_on_push is false, and the - # App bypasses the ruleset that carries it — so the workflow does it - # itself: approve, then push, and the PR is unapproved again. - approvers=$(printf '%s' "$latest" \ - | jq -r --arg sha "$sha" '.[] | select(.state == "APPROVED" and .commit == $sha) | .login') - - if [ -z "$approvers" ]; then - echo "PR #$n: no approval on the current head $sha — skipping." - continue - fi - - # An approval only gates anything if the approver could have landed - # this themselves. `authorAssociation` on the review is too coarse - # to lean on — COLLABORATOR covers read- and triage-only access. - approved=false - for login in $approvers; do - perm=$(gh api "repos/$GH_REPO/collaborators/$login/permission" --jq '.permission') || perm= - case "$perm" in - admin | maintain | write) - echo "PR #$n: approved on $sha by $login ($perm)." - approved=true - break - ;; - *) - echo "PR #$n: $login approved but has permission '${perm:-unreadable}' — not counted." - ;; - esac - done - - if [ "$approved" != "true" ]; then - echo "PR #$n: no approval from anyone with write access — skipping." - continue - fi - - echo "pr=$n" >> "$GITHUB_OUTPUT" - echo "sha=$sha" >> "$GITHUB_OUTPUT" - echo "PR #$n: green, approved queue PR $head -> $base at $sha." - break - done - - # No `environment:` — see the header. The gate is the approval `pick` verified, - # and it is pinned to a SHA: this job fast-forwards onto the exact commit that - # was approved, so a push landing between the two jobs cannot ride along. - fast-forward: - name: Fast-forward the integration branch - needs: pick - if: needs.pick.outputs.pr != '' - runs-on: ubuntu-latest - # Everything here runs on the App token; the workflow token needs nothing. - permissions: {} - steps: - # The PEM is never a GitHub secret — same runtime-from-Bitwarden pattern as - # release-please.yml. Unlike that one this uses the purpose-built "kirchDev - # Queue" App, which carries `contents: write` plus `pull_requests: write` - # (ADR-0010 in kirchDev/infrastructure, for the `open` job above) and nothing else, and is the - # merge-gate bypass actor on the integration branch - # (the SSOT in kirchDev/infrastructure). - # - # BWS_ACCESS_TOKEN is shared with this repo's other App-token workflows — - # org-level on kirchDev, a repo secret on the TitusKirch account. - - name: Fetch Queue App PEM from Bitwarden - uses: bitwarden/sm-action@1238aae8fc64b212641190a9227c8a734ab1a793 # v3.0.1 - with: - access_token: ${{ secrets.BWS_ACCESS_TOKEN }} - cloud_region: eu - secrets: | - df8b447a-ffd5-4009-9cc3-b49b014f6978 > KIRCHDEV_QUEUE_APP_PEM - - - name: Mint Queue App token - id: app-token - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 - with: - app-id: '4473889' - private-key: ${{ env.KIRCHDEV_QUEUE_APP_PEM }} - - # The App bypasses the merge gates only, so `required_signatures` from the - # integrity ruleset still binds every commit being fast-forwarded in: one - # unsigned worker commit anywhere in the queue branch fails this step with - # a 422, and the fix belongs on the worker's signing setup, not here. - # - # A ref PATCH without `force` is fast-forward-only: GitHub rejects it with - # 422 if the integration branch is not an ancestor of the queue head, which - # is exactly the guarantee wanted — the queue branch is rebased, never the - # history rewritten. GitHub closes the PR as merged once its head is - # reachable from the base. - - name: Fast-forward the integration branch - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - GH_REPO: ${{ github.repository }} - PR: ${{ needs.pick.outputs.pr }} - SHA: ${{ needs.pick.outputs.sha }} - run: | - set -euo pipefail - gh api -X PATCH "repos/$GH_REPO/git/refs/heads/$INTEGRATION_BRANCH" \ - -f sha="$SHA" -F force=false - { - echo "## Queue branch landed" - echo "" - echo "| Property | Value |" - echo "|----------|-------|" - echo "| PR | #$PR |" - echo "| Branch | \`$INTEGRATION_BRANCH\` |" - echo "| Commit | \`$SHA\` |" - } >> "$GITHUB_STEP_SUMMARY" + uses: kirchDev/workflows/.github/workflows/_fast-forward-queue.yml@f47f598f549a3c97924fdfd00f15371ea557e1aa # v0.1.1 + with: + pr: ${{ inputs.pr }} + secrets: + BWS_ACCESS_TOKEN: ${{ secrets.BWS_ACCESS_TOKEN }} diff --git a/.github/workflows/queue-branch.yml b/.github/workflows/queue-branch.yml index 5d3d14a..efd816c 100644 --- a/.github/workflows/queue-branch.yml +++ b/.github/workflows/queue-branch.yml @@ -1,175 +1,22 @@ # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +# This repo integrates on `main` — no `dev` branch. The body reads the +# integration branch off the pull request's own base, so nothing here says it +# twice. name: Queue Branch -# Cuts the queue branch a worktree drain's pull requests are grouped behind, and -# retargets each worker PR at it. This is the step the drain deliberately cannot -# take: `work.queueBranch` only makes a worker AIM at an open `ai/queue-*`, and -# the loop holds no write to that branch on purpose, so cutting it belongs to -# the repo. Without this workflow the key is inert — every worker PR falls back -# to the integration branch, which is exactly the mode-off path. See ADR-0012 in kirchDev/infrastructure. -# -# Which PRs are grouped is the RUN's to decide, not this repo's: only a drain -# started with `work.queueBranch: true` cuts its branches under `ai/queue/`, and -# only those reach the job below. A repo carrying this file therefore states -# that it CAN group, never that it always will. -# -# `fast-forward-queue.yml` owns the rest: its `open` job turns the pushed branch -# into a PR, and `pick` + `fast-forward` land that PR once it is green and -# approved. Nothing here opens a PR and nothing here lands one. on: pull_request: - # THE BASE IS FILTERED AT THE TRIGGER, not in a job-level `if`: that `if` - # cannot read `env`, so the integration branch would have to be repeated as - # a literal there. Keep this list and INTEGRATION_BRANCH in step. branches: - main - # `opened` only. A retarget is what this workflow DOES, so reacting to - # `edited` would have it answer its own edit; and a push to an already - # grouped PR needs nothing from here. types: [opened] -# Shared with fast-forward-queue.yml, not merely with other runs of this file. -# Cutting a branch and landing one must never interleave: a branch cut from an -# integration tip that a landing is about to move would no longer contain that -# tip, and the fast-forward window closes the moment it doesn't. -concurrency: - group: fast-forward-queue - cancel-in-progress: false - -env: - # The repo's integration branch — first entry of its branch_flow in the SSOT - # (tofu/data/github-{orgs,users}/*.yml in kirchDev/infrastructure). Change it - # here AND in the trigger's - # `branches:` above if that flow changes. - INTEGRATION_BRANCH: main - # The prefix fast-forward-queue.yml will accept as a head branch. Cutting - # anything else would produce a branch that workflow refuses to land. - QUEUE_BRANCH_PREFIX: ai/queue- - jobs: - cut: - name: Cut the queue branch - # THE `ai/queue/` PREFIX IS THE RUN'S OWN SIGNAL that it asked to be grouped. - # A drain under `work.queueBranch: false` cuts `ai/-` and is left - # alone here, so the profile the run was started with decides whether its - # PRs are grouped — not this repo merely carrying the workflow. Matching a - # bare `ai/` would group every worker PR, which makes the two fleet profiles - # indistinguishable in any repo that has this file. - # - # A fork's branch can be called `ai/queue/*` just as well, and a fork PR gets - # no secrets — the Bitwarden step would fail rather than skip. - # - # The queue PR itself needs no exclusion: the App opens it with an - # `ai/queue-` head, which does not match `ai/queue/` (hyphen, not - # slash), so it can never be retargeted onto itself. - if: >- - github.event.pull_request.head.repo.full_name == github.repository - && startsWith(github.event.pull_request.head.ref, 'ai/queue/') - runs-on: ubuntu-latest + queue-branch: + name: Queue Branch permissions: contents: read - # Retargeting the worker PR is the one write the workflow token makes; the - # branch itself is created on the App token below, for the reason given - # there. pull-requests: write - steps: - # Checked with the workflow token, so the App token is minted only when a - # branch actually has to be cut — every worker PR after the first finds - # the queue branch already open and stops before Bitwarden. - - name: Is a queue branch already open? - id: existing - env: - GH_TOKEN: ${{ github.token }} - GH_REPO: ${{ github.repository }} - run: | - set -euo pipefail - - # The OPEN PR is the marker, not the branch: a queue branch whose PR - # has landed is done, and its branch may well still exist. - open=$(gh pr list --state open --base "$INTEGRATION_BRANCH" \ - --json headRefName \ - --jq '[.[] | select(.headRefName | startswith(env.QUEUE_BRANCH_PREFIX))] | .[0].headRefName // ""') - - if [ -n "$open" ]; then - echo "Queue branch $open is already open — retargeting onto it." - echo "branch=$open" >> "$GITHUB_OUTPUT" - echo "create=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Named for the integration tip it is cut from, which is the one fact - # that decides whether it can still be fast-forwarded later. - tip=$(gh api "repos/$GH_REPO/git/ref/heads/$INTEGRATION_BRANCH" --jq '.object.sha') - echo "No open queue branch — cutting from $INTEGRATION_BRANCH at $tip." - echo "branch=$QUEUE_BRANCH_PREFIX${tip:0:7}" >> "$GITHUB_OUTPUT" - echo "sha=$tip" >> "$GITHUB_OUTPUT" - echo "create=true" >> "$GITHUB_OUTPUT" - - # Same runtime-from-Bitwarden pattern as fast-forward-queue.yml; the PEM is - # never a GitHub secret. BWS_ACCESS_TOKEN is org-level on kirchDev and a repo - # secret on the TitusKirch account. - - name: Fetch Queue App PEM from Bitwarden - if: steps.existing.outputs.create == 'true' - uses: bitwarden/sm-action@1238aae8fc64b212641190a9227c8a734ab1a793 # v3.0.1 - with: - access_token: ${{ secrets.BWS_ACCESS_TOKEN }} - cloud_region: eu - secrets: | - df8b447a-ffd5-4009-9cc3-b49b014f6978 > KIRCHDEV_QUEUE_APP_PEM - - - name: Mint Queue App token - id: app-token - if: steps.existing.outputs.create == 'true' - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 - with: - app-id: '4473889' - private-key: ${{ env.KIRCHDEV_QUEUE_APP_PEM }} - - # THE APP TOKEN IS WHAT MAKES THE NEXT WORKFLOW FIRE, and that is the whole - # reason it is minted here rather than pushing the ref with the workflow - # token. A ref created with GITHUB_TOKEN raises no `push` event, so - # fast-forward-queue.yml's `open` job would never run and the branch would - # sit there with no PR — the exact dead end this workflow exists to close. - # - # Nothing is checked out in this job, so no branch content ever executes - # alongside the token (the same rule the `open` job is written under). - - name: Cut the queue branch - if: steps.existing.outputs.create == 'true' - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - GH_REPO: ${{ github.repository }} - BRANCH: ${{ steps.existing.outputs.branch }} - SHA: ${{ steps.existing.outputs.sha }} - run: | - set -euo pipefail - gh api -X POST "repos/$GH_REPO/git/refs" \ - -f ref="refs/heads/$BRANCH" -f sha="$SHA" - echo "Cut $BRANCH at $SHA." - - # Done on the workflow token deliberately: a base change is not a push, so - # nothing downstream needs to be woken by it, and the App token stays out - # of every step that doesn't need it. - # - # This is also where the CI saving lands. where `ci.yml` is - # scoped to the integration branches: once the base is `ai/queue-*` - # further pushes to the worker PR run nothing — CI runs once, on the queue - # PR, instead of once per issue. - - name: Retarget the worker PR - env: - GH_TOKEN: ${{ github.token }} - GH_REPO: ${{ github.repository }} - PR: ${{ github.event.pull_request.number }} - BRANCH: ${{ steps.existing.outputs.branch }} - run: | - set -euo pipefail - gh pr edit "$PR" --base "$BRANCH" - { - echo "## Worker PR grouped" - echo "" - echo "| Property | Value |" - echo "|----------|-------|" - echo "| PR | #$PR |" - echo "| Base | \`$BRANCH\` |" - echo "| Cut this run | \`${{ steps.existing.outputs.create }}\` |" - } >> "$GITHUB_STEP_SUMMARY" + uses: kirchDev/workflows/.github/workflows/_queue-branch.yml@f47f598f549a3c97924fdfd00f15371ea557e1aa # v0.1.1 + secrets: + BWS_ACCESS_TOKEN: ${{ secrets.BWS_ACCESS_TOKEN }} From a555df9ae929c170c41bf56d2e50ce4953b12eaa Mon Sep 17 00:00:00 2001 From: Titus Kirch Date: Tue, 1 Sep 2026 21:40:28 +0200 Subject: [PATCH 2/2] fix(ci): bump the bodies to v0.1.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bodies invoked by one caller shared a concurrency group and cancelled each other — github.workflow resolves to the CALLER's name inside a called workflow, so every body using the conventional expression collided. v0.1.2 gives each group the body's own name. --- .github/workflows/ci.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/fast-forward-queue.yml | 2 +- .github/workflows/queue-branch.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ad6ad8..9d4860b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,4 +20,4 @@ permissions: jobs: check: name: CI - uses: kirchDev/workflows/.github/workflows/_ci-check.yml@f47f598f549a3c97924fdfd00f15371ea557e1aa # v0.1.1 + uses: kirchDev/workflows/.github/workflows/_ci-check.yml@c2c3c73a1b38c10e5d4a6dfa76c46749c03c1158 # v0.1.2 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index fe949d3..5af22c1 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -20,4 +20,4 @@ jobs: packages: read actions: read contents: read - uses: kirchDev/workflows/.github/workflows/_codeql.yml@f47f598f549a3c97924fdfd00f15371ea557e1aa # v0.1.1 + uses: kirchDev/workflows/.github/workflows/_codeql.yml@c2c3c73a1b38c10e5d4a6dfa76c46749c03c1158 # v0.1.2 diff --git a/.github/workflows/fast-forward-queue.yml b/.github/workflows/fast-forward-queue.yml index 32f093b..448171b 100644 --- a/.github/workflows/fast-forward-queue.yml +++ b/.github/workflows/fast-forward-queue.yml @@ -26,7 +26,7 @@ jobs: pull-requests: read checks: read statuses: read - uses: kirchDev/workflows/.github/workflows/_fast-forward-queue.yml@f47f598f549a3c97924fdfd00f15371ea557e1aa # v0.1.1 + uses: kirchDev/workflows/.github/workflows/_fast-forward-queue.yml@c2c3c73a1b38c10e5d4a6dfa76c46749c03c1158 # v0.1.2 with: pr: ${{ inputs.pr }} secrets: diff --git a/.github/workflows/queue-branch.yml b/.github/workflows/queue-branch.yml index efd816c..fa5bab3 100644 --- a/.github/workflows/queue-branch.yml +++ b/.github/workflows/queue-branch.yml @@ -17,6 +17,6 @@ jobs: permissions: contents: read pull-requests: write - uses: kirchDev/workflows/.github/workflows/_queue-branch.yml@f47f598f549a3c97924fdfd00f15371ea557e1aa # v0.1.1 + uses: kirchDev/workflows/.github/workflows/_queue-branch.yml@c2c3c73a1b38c10e5d4a6dfa76c46749c03c1158 # v0.1.2 secrets: BWS_ACCESS_TOKEN: ${{ secrets.BWS_ACCESS_TOKEN }}