name: Bump Plugin SHAs # Nightly sweep: for each external entry whose upstream HEAD has moved past # its pinned SHA, validate at the new SHA with `claude plugin validate` # inline, then open one PR per bumped plugin on branch `bump/`. # Failing entries stay isolated in their own PR; passing bumps merge # independently. (Cohort-2 cutover from the previous single-batch # `bump/plugin-shas` PR — mirrors claude-plugins-official / -community.) # # Bot-free — uses the default GITHUB_TOKEN. PRs opened with GITHUB_TOKEN don't # trigger on:pull_request workflows, so the policy scan (`scan` from Scan # Plugins, a required status check on main) would never run and the bump PR # could never merge. workflow_dispatch is exempt from that recursion guard, so # we dispatch the scan ourselves against each per-entry bump branch after its # PR is opened. Each check run lands on the branch HEAD — the same SHA as the # PR head — and satisfies the required check. (Scan Plugins runs its job # unconditionally on workflow_dispatch, so a dispatch always reports.) # # IMPORTANT — dispatch `scan-plugins` ONLY. Unlike claude-plugins-official # (which requires `scan`+`check`+`validate` and fans out all three), KWP's only # bump-blocking required check is `scan`: this repo has NO validate-plugins.yml, # and check-mcp-urls is NOT a required status check (it is local-source-only + # path-filtered — skips SHA-pinned externals — and self-schedules). Do NOT copy # official's 3-workflow loop here — `gh workflow run validate-plugins.yml` would # 404 and fail the step nightly, and dispatching check-mcp-urls is needless. # # max-bumps caps the per-night work for cost control. Per-entry scans are more # expensive than a single batched scan (one workflow run per bump branch), so # the cap is conservative. The composite action skips entries that already have # an open bump PR, so re-dispatches don't pile up duplicate work. # - scan-plugins.yml caches verdicts by (plugin, sha) so an unchanged SHA # is never re-scanned across nightly runs. # Per-entry failure handling: a policy-failing plugin's `bump/` PR stays # isolated (red on its own scan) and never blocks the others — there is no # shared PR to prune, so revert-failed-bumps.yml (still gated on the old # `bump/plugin-shas` branch) is inert under per-entry, by design; a failing # entry is left for human triage of its own PR. on: schedule: - cron: '23 7 * * *' # Daily 07:23 UTC workflow_dispatch: inputs: max_bumps: description: Cap on plugins bumped this run required: false default: '30' plugin: description: >- Bump ONLY this plugin name (exact entry name; empty = all stale). A frozen/sha-exempt target is still skipped (same as a full run). required: false default: '' permissions: contents: write pull-requests: write actions: write # gh workflow run scan-plugins.yml per per-entry bump branch concurrency: group: bump-plugin-shas jobs: bump: runs-on: ubuntu-latest # Per-bump cost is ~2s (ls-remote + shallow clone + validate); 30 entries # is ~1-2 min. The 60 min ceiling absorbs slow upstreams without letting a # pathological run consume the default 360 min budget. timeout-minutes: 60 steps: - uses: actions/checkout@v4 # createCommitOnBranch-based bump so commits are signed by GitHub and # satisfy the org-level required_signatures ruleset on main. - uses: anthropics/claude-plugins-community/.github/actions/bump-plugin-shas@426e469f322952061102b286b378c0c9733a0934 id: bump with: marketplace-path: .claude-plugin/marketplace.json max-bumps: ${{ inputs.max_bumps || '30' }} only: ${{ inputs.plugin }} pr-mode: per-entry claude-cli-version: latest # Per-entry fan-out: dispatch the policy scan against each bump branch. # `pr-urls` is a JSON array of {name, old_sha, new_sha, branch, pr_url} # entries emitted by the composite action when pr-mode is per-entry. The # `scan` check is required on main and does NOT fire on the # GITHUB_TOKEN-opened PR, so it must be dispatched per branch. # # Dispatch `scan-plugins` ONLY (see header) — NOT official's 3-workflow # loop. A single failed dispatch (transient API error / rate limit) must # not strand the remaining branches, so we attempt every dispatch, then # fail the step if any failed: a missing required check would otherwise # leave its bump PR silently blocked behind a green run, and the composite # action skips slugs with an open PR so it would never be retried. The # failure list MUST be a tmpfile (the `jq | while` loop runs in a # subshell, so a shell-variable counter would be lost on subshell exit). - name: Dispatch policy scan per per-entry PR if: steps.bump.outputs.pr-urls != '' && steps.bump.outputs.pr-urls != '[]' env: GH_TOKEN: ${{ github.token }} PR_URLS: ${{ steps.bump.outputs.pr-urls }} run: | set -euo pipefail dispatch_failures="$(mktemp)" jq -c '.[]' <<<"$PR_URLS" | while read -r entry; do branch=$(jq -r '.branch' <<<"$entry") name=$(jq -r '.name' <<<"$entry") echo "Dispatching scan-plugins.yml against $branch ($name)" if ! gh workflow run scan-plugins.yml --ref "$branch"; then echo "::error::Failed to dispatch scan-plugins.yml against $branch ($name) — required check will be missing; re-dispatch with: gh workflow run scan-plugins.yml --ref $branch" echo "scan-plugins ${branch}" >> "$dispatch_failures" fi done if [ -s "$dispatch_failures" ]; then echo "::error::$(wc -l < "$dispatch_failures" | tr -d ' ') scan dispatch(es) failed; the affected bump PR(s) are blocked until re-dispatched (see annotations above)." exit 1 fi