Skip to content

ci: gate external plugin entries on community scan merge#569

Open
tobinsouth wants to merge 1 commit intostagingfrom
ci/verify-community-merged
Open

ci: gate external plugin entries on community scan merge#569
tobinsouth wants to merge 1 commit intostagingfrom
ci/verify-community-merged

Conversation

@tobinsouth
Copy link
Copy Markdown
Collaborator

@tobinsouth tobinsouth commented Mar 9, 2026

What

Adds .github/workflows/verify-community-merged.yml — a stateless check that fails any PR adding an external plugin entry to marketplace.json unless that entry (same name, same source.sha) is already merged on claude-plugins-community main.

Why

This repo runs no security scan on external plugins. The scan lives in claude-plugins-community. Currently the only thing coupling the two is a PR body convention (Community link: #N) and a human remembering to check. This makes the invariant structural.

How it works

  1. Diff marketplace.json base→head, extract external entries (those where .source is an object, not a vendored ./path string)
  2. Key each by {name, sha} — that pair is what the community scan result is anchored to
  3. comm -13 to find added keys (new entries or SHA bumps)
  4. Fetch community main via gh api and check each added key is present

What it catches

Scenario Result
New external entry, community PR merged ✅ pass
New external entry, community PR not merged yet ❌ fail — "not found in community main, merge community PR first"
SHA bump on existing entry, no community re-scan ❌ fail — "exists at SHA abc, not def — re-pin or re-scan"
New entry with no source.sha ❌ fail — "entries must be SHA-pinned"
Description/category/homepage edit ✅ pass (cosmetic, not keyed)
Entry removal ✅ pass (no scan needed to delete)
Vendored entry (source: "./path") ✅ skipped (authored in-repo, reviewed here)
PR touches other files, not marketplace.json ⏭ workflow doesn't run (path filter)

Edge cases handled

  • Community repo goes private: gh api uses the workflow's GITHUB_TOKEN, works same-org regardless of visibility. No PAT needed.
  • raw.githubusercontent flakiness: avoided — gh api instead of curl (was hitting exit 56 recv failures in local testing)
  • Pre-invariant null-SHA entries in this repo: they're in base too, so they don't appear in the added diff. Only new null-SHA entries fail.
  • Re-run after community merges: check is stateless, re-run → pass. No state to reset.

Tested

Simulated locally against live marketplace data:

  • Added pagerduty entry → correctly fails (not in community main)
  • SHA bump on qodo-skills → correctly detected as added
  • Description-only edit → correctly produces empty diff
  • Removal → correctly produces empty diff

Companion

Identical workflow going into knowledge-work-plugins — see anthropics/knowledge-work-plugins anthropics/knowledge-work-plugins#103.

Adds a stateless check that fails if any added external marketplace.json
entry (keyed by name+sha) is not already present on
claude-plugins-community main.

This repo runs no security scan — the scan is in claude-plugins-community.
Without this check, the only thing preventing a bypass is the PR body
convention of linking to a community PR, and a human remembering to look.

The check:
- Diffs marketplace.json base→head, extracts external entries (source is
  an object, not a vendored string path), keyed by {name, sha}
- Fails if any added key is absent from community main
- Catches new entries AND sha bumps (new sha → new scan required)
- Skips cosmetic edits (description/category) and removals
- Gives a precise diagnosis on failure: SHA mismatch vs entry absent
- Rejects new entries with no sha pin (scan anchor is meaningless)

Fetch uses gh api with the workflow token, not raw.githubusercontent
(which flakes with curl exit 56). Works same-org whether community is
public or private.
@thepointer1982
Copy link
Copy Markdown

Hi

@tobinsouth tobinsouth changed the base branch from main to staging March 13, 2026 21:11
uses: actions/checkout@v4
with:
# Need base ref too, to diff and find what's new
fetch-depth: 0
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reduce fetch-depth from 0 to something minimal

fail=0
while IFS= read -r entry; do
name=$(jq -r .name <<< "$entry")
sha=$(jq -r '.sha // "∅"' <<< "$entry")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Replace ∅ sentinel with ASCII equivalent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants