What Is the `bump-plugin-shas` GitHub Action? A Deep Dive into Automated SHA Management for Claude Plugins
The bump-plugin-shas GitHub Action automatically updates pinned SHA commits for external Claude plugins in the marketplace manifest, validates the new code, and opens pull requests for maintainer review.
This custom action in the anthropics/claude-plugins-community repository solves a critical maintenance problem: keeping third-party plugin references current without manual intervention. When upstream repositories move forward, this action detects changes, ensures plugin integrity through validation, and creates properly signed commits for security-conscious merging.
How bump-plugin-shas Discovers Outdated Plugin SHAs
The action begins by scanning .claude-plugin/marketplace.json for every external plugin entry that contains a source object. These entries point to remote Git repositories that require SHA pinning for reproducible builds.
For each discovered plugin, the action determines the target reference:
- Default tracking: Compares against the repository's current
HEAD - Releases-only tracking: Checks the latest published GitHub release (configured via
tracking-config)
This dual-mode approach lets maintainers choose stability (releases) or bleeding-edge updates (HEAD) on a per-plugin basis.
Validation and Safety Guards in bump-plugin-shas
Before any SHA advances, the action enforces a multi-layer protection system:
Validation Pipeline
When a newer commit exists, the action clones the repository, checks out the candidate SHA, and runs claude plugin validate against the plugin manifest. For plugins using "strict": false, it synthesizes a minimal manifest to ensure basic structural compliance. Failed validations abort the bump for that plugin—no broken code enters the manifest.
Policy Enforcement Rules
| Guard | Purpose | Location |
|---|---|---|
| sha-exempt | Lists plugins deliberately without SHA pins; completely ignored | Configurable list |
| freeze-shas | Plugins frozen at current SHA; logged warning, never bumped | .github/freeze-shas.txt |
| tracking-config | JSON file specifying "releases-only" plugins |
Repository config |
| owner-baseline | Verifies repository owner account ID matches recorded value | .github/owner-baseline.json |
The owner baseline check prevents a subtle attack vector: if a repository transfers ownership, the action detects the mismatch and stops automatic pinning to the new owner's code.
PR Creation Modes: Batch vs. Per-Entry
The pr-mode input controls how changes reach maintainers:
batch mode creates a single signed commit on bump/plugin-shas (configurable) containing all successful bumps—efficient for routine maintenance with trusted plugins.
per-entry mode isolates each bump on its own branch (bump/<sanitized-name>) with individual PRs. This prevents one validation failure from blocking ten healthy updates. The repository's default workflow uses this safer approach.
Both modes use GitHub's GraphQL createCommitOnBranch mutation for server-side signed commits, satisfying the organization's required_signatures rule without exposing private keys to runners.
Using bump-plugin-shas in Your Workflow
Nightly Automated Run
# .github/workflows/bump-plugin-shas.yml (excerpt)
- uses: ./.github/actions/bump-plugin-shas
id: bump
with:
marketplace-path: .claude-plugin/marketplace.json
max-bumps: ${{ inputs.max_bumps || '30' }}
freeze-shas: ${{ steps.freeze.outputs.list }}
only: ${{ inputs.plugin }}
pr-mode: per-entry
claude-cli-version: latest
The workflow triggers on cron schedule and manual dispatch, loading frozen pins before execution.
Single-Plugin Manual Trigger
workflow_dispatch:
inputs:
max_bumps:
description: 'Cap on plugins bumped this run'
default: '30'
plugin:
description: 'Bump ONLY this plugin name (empty = all stale)'
default: ''
Supply a specific plugin name through the Actions UI for targeted updates—useful for urgent security patches or debugging.
Core Implementation in bump.sh
The discovery and validation logic lives in scripts/bump.sh:
# Simplified discovery loop from bump.sh lines 67-108
while IFS= read -r entry; do
name=$(jq -r '.name' <<<"$entry")
# Skip guards: sha-exempt, frozen, owner mismatch...
new_sha=$(git ls-remote "$full_url" HEAD | awk '{print $1}')
[[ "$new_sha" == "$old_sha" ]] && continue
# Validate before accepting
claude plugin validate "$manifest"
# Record for commit/PR creation
bumps+=("$name:$old_sha:$new_sha")
done < <(jq -c '.plugins[] | select(.source|type=="object")' "$MARKETPLACE_PATH")
This loop handles HEAD checks, release-only overrides, subtree suppression, validation gating, and finally delegates to the commit signing logic at lines 35-44.
Action Outputs and Observability
After execution, bump-plugin-shas exposes structured data:
bumped: JSON array of successful upgrades withname,old_sha,new_shaskipped: Array of rejected bumps with explanatoryreasonpr-url/pr-urls: Direct links to created pull request(s)
Downstream jobs can consume these outputs for notifications, metrics dashboards, or additional validation pipelines.
Summary
bump-plugin-shasautomates SHA pin maintenance for external Claude plugins through discovery, validation, and policy-guarded PR creation- Two tracking modes support both HEAD-following and release-only update strategies
- Four safety guards (sha-exempt, freeze-shas, tracking-config, owner-baseline) prevent unwanted or malicious updates
- Server-side signed commits via
createCommitOnBranchmaintain GPG verification without key exposure - Per-entry PR mode isolates failures; batch mode optimizes for trusted plugins
- Primary implementation spans
.github/actions/bump-plugin-shas/action.ymlandscripts/bump.sh
Frequently Asked Questions
What triggers the bump-plugin-shas action to run?
The action runs on a scheduled cron (nightly by default) and via workflow_dispatch for manual execution. The triggering workflow at .github/workflows/bump-plugin-shas.yml loads configuration, prepares the freeze list, then invokes the action with appropriate inputs.
How does bump-plugin-shas handle plugin validation failures?
Validation failures skip the individual bump without aborting the entire run. The plugin name and failure reason populate the skipped output array. In per-entry PR mode, other plugins still receive their own PRs; in batch mode, the failing plugin is simply omitted from the collective commit.
Why does the action use server-side commit signing instead of GPG keys on the runner?
The createCommitOnBranch GraphQL mutation generates commits with verified signatures directly through GitHub's API. This satisfies the required_signatures branch protection rule while eliminating the security risk of private signing keys in CI environments—no key material ever touches the action runner.
Can I prevent bump-plugin-shas from updating specific plugins?
Yes. Add plugin names to .github/freeze-shas.txt to freeze them at current SHAs, or to the sha-exempt list to ignore them entirely. For release stability, add plugins to the "releases-only" array in your tracking configuration file.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →